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.api.plugin.testing.stubs;
20  
21  import java.net.URI;
22  import java.nio.file.Path;
23  import java.nio.file.Paths;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Optional;
31  import java.util.Properties;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.function.Supplier;
34  
35  import org.apache.maven.api.Artifact;
36  import org.apache.maven.api.LocalRepository;
37  import org.apache.maven.api.ProducedArtifact;
38  import org.apache.maven.api.Project;
39  import org.apache.maven.api.RemoteRepository;
40  import org.apache.maven.api.Session;
41  import org.apache.maven.api.SessionData;
42  import org.apache.maven.api.annotations.Nonnull;
43  import org.apache.maven.api.model.Model;
44  import org.apache.maven.api.model.Repository;
45  import org.apache.maven.api.services.ArtifactDeployer;
46  import org.apache.maven.api.services.ArtifactDeployerRequest;
47  import org.apache.maven.api.services.ArtifactFactory;
48  import org.apache.maven.api.services.ArtifactFactoryRequest;
49  import org.apache.maven.api.services.ArtifactInstaller;
50  import org.apache.maven.api.services.ArtifactInstallerRequest;
51  import org.apache.maven.api.services.ArtifactManager;
52  import org.apache.maven.api.services.LocalRepositoryManager;
53  import org.apache.maven.api.services.Lookup;
54  import org.apache.maven.api.services.ProjectBuilder;
55  import org.apache.maven.api.services.ProjectBuilderRequest;
56  import org.apache.maven.api.services.ProjectBuilderResult;
57  import org.apache.maven.api.services.ProjectManager;
58  import org.apache.maven.api.services.RepositoryFactory;
59  import org.apache.maven.api.services.VersionParser;
60  import org.apache.maven.api.services.xml.ModelXmlFactory;
61  import org.apache.maven.impl.DefaultModelVersionParser;
62  import org.apache.maven.impl.DefaultModelXmlFactory;
63  import org.apache.maven.impl.DefaultVersionParser;
64  import org.apache.maven.impl.InternalSession;
65  import org.apache.maven.model.v4.MavenStaxReader;
66  import org.eclipse.aether.util.version.GenericVersionScheme;
67  import org.mockito.ArgumentMatchers;
68  import org.mockito.quality.Strictness;
69  
70  import static org.mockito.ArgumentMatchers.any;
71  import static org.mockito.ArgumentMatchers.anyString;
72  import static org.mockito.ArgumentMatchers.same;
73  import static org.mockito.Mockito.doAnswer;
74  import static org.mockito.Mockito.doReturn;
75  import static org.mockito.Mockito.mock;
76  import static org.mockito.Mockito.when;
77  import static org.mockito.Mockito.withSettings;
78  
79  /**
80   * A mock implementation of {@link InternalSession} for testing Maven plugins.
81   * This class provides a comprehensive mock session that simulates the behavior
82   * of a real Maven session, including repository management, artifact handling,
83   * and project building capabilities.
84   *
85   * <p>The mock session includes pre-configured behaviors for:</p>
86   * <ul>
87   *   <li>Repository management (local and remote repositories)</li>
88   *   <li>Artifact installation and deployment</li>
89   *   <li>Project building and management</li>
90   *   <li>Version parsing and handling</li>
91   *   <li>Model XML processing</li>
92   *   <li>Session data storage</li>
93   * </ul>
94   *
95   * <p>Example usage in a test:</p>
96   * <pre>
97   * {@code
98   * @Test
99   * void testMojo() {
100  *     // Create a mock session with a specific local repository
101  *     InternalSession session = SessionMock.getMockSession("/path/to/local/repo");
102  *
103  *     // Use the session for testing
104  *     MyMojo mojo = new MyMojo();
105  *     mojo.setSession(session);
106  *     mojo.execute();
107  * }
108  * }
109  * </pre>
110  *
111  * <p>The mock session maintains internal state for:</p>
112  * <ul>
113  *   <li>Attached artifacts (via {@link ProjectManager})</li>
114  *   <li>Artifact paths (via {@link ArtifactManager})</li>
115  *   <li>System and user properties</li>
116  *   <li>Session-scoped data (via {@link TestSessionData})</li>
117  * </ul>
118  *
119  * <p>Most service implementations are mocked using Mockito, with pre-configured
120  * behaviors that simulate typical Maven operations. Some services, like
121  * {@link ModelXmlFactory} and {@link VersionParser}, use real implementations
122  * to ensure correct handling of Maven models and versions.</p>
123  *
124  * @see InternalSession
125  * @see LocalRepository
126  * @see ProjectManager
127  * @see ArtifactManager
128  * @since 4.0.0
129  */
130 public class SessionMock {
131 
132     public static InternalSession getMockSession(String localRepo) {
133         LocalRepository localRepository = mock(LocalRepository.class);
134         when(localRepository.getId()).thenReturn("local");
135         when(localRepository.getPath()).thenReturn(Paths.get(localRepo));
136         return getMockSession(localRepository);
137     }
138 
139     @SuppressWarnings("checkstyle:MethodLength")
140     public static InternalSession getMockSession(LocalRepository localRepository) {
141         InternalSession session = mock(InternalSession.class);
142 
143         //
144         // RepositoryFactory
145         //
146         RepositoryFactory repositoryFactory = mock(RepositoryFactory.class);
147         when(session.createRemoteRepository(anyString(), anyString())).thenAnswer(iom -> {
148             String id = iom.getArgument(0, String.class);
149             String url = iom.getArgument(1, String.class);
150             return session.getService(RepositoryFactory.class).createRemote(id, url);
151         });
152         when(session.createRemoteRepository(any()))
153                 .thenAnswer(iom -> repositoryFactory.createRemote(iom.getArgument(0, Repository.class)));
154         when(repositoryFactory.createRemote(any(Repository.class))).thenAnswer(iom -> {
155             Repository repository = iom.getArgument(0, Repository.class);
156             return repositoryFactory.createRemote(repository.getId(), repository.getUrl());
157         });
158         when(repositoryFactory.createRemote(anyString(), anyString())).thenAnswer(iom -> {
159             String id = iom.getArgument(0, String.class);
160             String url = iom.getArgument(1, String.class);
161             RemoteRepository remoteRepository =
162                     mock(RemoteRepository.class, withSettings().strictness(Strictness.LENIENT));
163             when(remoteRepository.getId()).thenReturn(id);
164             when(remoteRepository.getUrl()).thenReturn(url);
165             when(remoteRepository.getProtocol()).thenReturn(URI.create(url).getScheme());
166             return remoteRepository;
167         });
168         when(session.getService(RepositoryFactory.class)).thenReturn(repositoryFactory);
169 
170         //
171         // VersionParser
172         //
173         VersionParser versionParser =
174                 new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme()));
175         when(session.parseVersion(any()))
176                 .thenAnswer(iom -> versionParser.parseVersion(iom.getArgument(0, String.class)));
177         when(session.getService(VersionParser.class)).thenReturn(versionParser);
178 
179         //
180         // LocalRepositoryManager
181         //
182         LocalRepositoryManager localRepositoryManager = mock(LocalRepositoryManager.class);
183         when(session.getPathForLocalArtifact(any(Artifact.class)))
184                 .then(iom -> localRepositoryManager.getPathForLocalArtifact(
185                         session, session.getLocalRepository(), iom.getArgument(0, Artifact.class)));
186         when(session.getPathForRemoteArtifact(any(), any()))
187                 .thenAnswer(iom -> localRepositoryManager.getPathForRemoteArtifact(
188                         session,
189                         session.getLocalRepository(),
190                         iom.getArgument(0, RemoteRepository.class),
191                         iom.getArgument(1, Artifact.class)));
192         when(localRepositoryManager.getPathForLocalArtifact(any(), any(), any()))
193                 .thenAnswer(iom -> {
194                     LocalRepository localRepo = iom.getArgument(1, LocalRepository.class);
195                     Artifact artifact = iom.getArgument(2, Artifact.class);
196                     return localRepo.getPath().resolve(getPathForArtifact(artifact));
197                 });
198         when(session.getService(LocalRepositoryManager.class)).thenReturn(localRepositoryManager);
199 
200         //
201         // ArtifactInstaller
202         //
203         ArtifactInstaller artifactInstaller = mock(ArtifactInstaller.class);
204         doAnswer(iom -> {
205                     artifactInstaller.install(
206                             ArtifactInstallerRequest.build(session, iom.getArgument(0, Collection.class)));
207                     return null;
208                 })
209                 .when(session)
210                 .installArtifacts(any(Collection.class));
211         doAnswer(iom -> {
212                     artifactInstaller.install(ArtifactInstallerRequest.build(
213                             session, Arrays.asList(iom.getArgument(0, ProducedArtifact[].class))));
214                     return null;
215                 })
216                 .when(session)
217                 .installArtifacts(any(ProducedArtifact[].class));
218         doAnswer(iom -> {
219                     artifactInstaller.install(ArtifactInstallerRequest.build(
220                             iom.getArgument(0, Session.class), iom.getArgument(1, Collection.class)));
221                     return null;
222                 })
223                 .when(artifactInstaller)
224                 .install(any(Session.class), ArgumentMatchers.<Collection<ProducedArtifact>>any());
225         when(session.getService(ArtifactInstaller.class)).thenReturn(artifactInstaller);
226 
227         //
228         // ArtifactDeployer
229         //
230         ArtifactDeployer artifactDeployer = mock(ArtifactDeployer.class);
231         doAnswer(iom -> {
232                     artifactDeployer.deploy(ArtifactDeployerRequest.build(
233                             iom.getArgument(0, Session.class),
234                             iom.getArgument(1, RemoteRepository.class),
235                             Arrays.asList(iom.getArgument(2, ProducedArtifact[].class))));
236                     return null;
237                 })
238                 .when(session)
239                 .deployArtifact(any(), any());
240         doAnswer(iom -> {
241                     artifactDeployer.deploy(ArtifactDeployerRequest.build(
242                             iom.getArgument(0, Session.class),
243                             iom.getArgument(1, RemoteRepository.class),
244                             iom.getArgument(2, Collection.class)));
245                     return null;
246                 })
247                 .when(artifactDeployer)
248                 .deploy(any(), any(), any());
249         when(session.getService(ArtifactDeployer.class)).thenReturn(artifactDeployer);
250 
251         //
252         // ArtifactManager
253         //
254         ArtifactManager artifactManager = mock(ArtifactManager.class);
255         Map<Artifact, Path> paths = new HashMap<>();
256         doAnswer(iom -> {
257                     paths.put(iom.getArgument(0), iom.getArgument(1));
258                     return null;
259                 })
260                 .when(artifactManager)
261                 .setPath(any(), any());
262         doAnswer(iom -> Optional.ofNullable(paths.get(iom.getArgument(0, Artifact.class))))
263                 .when(artifactManager)
264                 .getPath(any());
265         doAnswer(iom -> artifactManager.getPath(iom.getArgument(0, Artifact.class)))
266                 .when(session)
267                 .getArtifactPath(any());
268         when(session.getService(ArtifactManager.class)).thenReturn(artifactManager);
269 
270         //
271         // ProjectManager
272         //
273         ProjectManager projectManager = mock(ProjectManager.class);
274         Map<Project, Collection<Artifact>> attachedArtifacts = new HashMap<>();
275         doAnswer(iom -> {
276                     Project project = iom.getArgument(1, Project.class);
277                     String type = iom.getArgument(2, String.class);
278                     Path path = iom.getArgument(3, Path.class);
279                     ProducedArtifact artifact = session.createProducedArtifact(
280                             project.getGroupId(), project.getArtifactId(), project.getVersion(), null, null, type);
281                     artifactManager.setPath(artifact, path);
282                     attachedArtifacts
283                             .computeIfAbsent(project, p -> new ArrayList<>())
284                             .add(artifact);
285                     return null;
286                 })
287                 .when(projectManager)
288                 .attachArtifact(same(session), any(Project.class), any(), any());
289         doAnswer(iom -> {
290                     Project project = iom.getArgument(0, Project.class);
291                     ProducedArtifact artifact = iom.getArgument(1, ProducedArtifact.class);
292                     Path path = iom.getArgument(2, Path.class);
293                     artifactManager.setPath(artifact, path);
294                     attachedArtifacts
295                             .computeIfAbsent(project, p -> new ArrayList<>())
296                             .add(artifact);
297                     return null;
298                 })
299                 .when(projectManager)
300                 .attachArtifact(any(Project.class), any(ProducedArtifact.class), any(Path.class));
301         when(projectManager.getAttachedArtifacts(any()))
302                 .then(iom ->
303                         attachedArtifacts.computeIfAbsent(iom.getArgument(0, Project.class), p -> new ArrayList<>()));
304         when(projectManager.getAllArtifacts(any())).then(iom -> {
305             Project project = iom.getArgument(0, Project.class);
306             List<Artifact> result = new ArrayList<>();
307             result.addAll(project.getArtifacts());
308             result.addAll(attachedArtifacts.computeIfAbsent(project, p -> new ArrayList<>()));
309             return result;
310         });
311         when(session.getService(ProjectManager.class)).thenReturn(projectManager);
312 
313         //
314         // ArtifactFactory
315         //
316         ArtifactFactory artifactFactory = mock(ArtifactFactory.class);
317         when(artifactFactory.create(any())).then(iom -> {
318             ArtifactFactoryRequest request = iom.getArgument(0, ArtifactFactoryRequest.class);
319             String classifier = request.getClassifier();
320             String extension = request.getExtension();
321             String type = request.getType();
322             if (classifier == null) {
323                 classifier = "";
324             }
325             if (extension == null) {
326                 extension = type != null ? type : "";
327             }
328             return new ArtifactStub(
329                     request.getGroupId(), request.getArtifactId(), classifier, request.getVersion(), extension);
330         });
331         when(artifactFactory.createProduced(any())).then(iom -> {
332             ArtifactFactoryRequest request = iom.getArgument(0, ArtifactFactoryRequest.class);
333             String classifier = request.getClassifier();
334             String extension = request.getExtension();
335             String type = request.getType();
336             if (classifier == null) {
337                 classifier = "";
338             }
339             if (extension == null) {
340                 extension = type != null ? type : "";
341             }
342             return new ProducedArtifactStub(
343                     request.getGroupId(), request.getArtifactId(), classifier, request.getVersion(), extension);
344         });
345         when(session.createArtifact(any(), any(), any(), any(), any(), any())).thenAnswer(iom -> {
346             String groupId = iom.getArgument(0, String.class);
347             String artifactId = iom.getArgument(1, String.class);
348             String version = iom.getArgument(2, String.class);
349             String classifier = iom.getArgument(3, String.class);
350             String extension = iom.getArgument(4, String.class);
351             String type = iom.getArgument(5, String.class);
352             return session.getService(ArtifactFactory.class)
353                     .create(ArtifactFactoryRequest.builder()
354                             .session(session)
355                             .groupId(groupId)
356                             .artifactId(artifactId)
357                             .version(version)
358                             .classifier(classifier)
359                             .extension(extension)
360                             .type(type)
361                             .build());
362         });
363         when(session.createArtifact(any(), any(), any(), any())).thenAnswer(iom -> {
364             String groupId = iom.getArgument(0, String.class);
365             String artifactId = iom.getArgument(1, String.class);
366             String version = iom.getArgument(2, String.class);
367             String extension = iom.getArgument(3, String.class);
368             return session.getService(ArtifactFactory.class)
369                     .create(ArtifactFactoryRequest.builder()
370                             .session(session)
371                             .groupId(groupId)
372                             .artifactId(artifactId)
373                             .version(version)
374                             .extension(extension)
375                             .build());
376         });
377         when(session.createProducedArtifact(any(), any(), any(), any(), any(), any()))
378                 .thenAnswer(iom -> {
379                     String groupId = iom.getArgument(0, String.class);
380                     String artifactId = iom.getArgument(1, String.class);
381                     String version = iom.getArgument(2, String.class);
382                     String classifier = iom.getArgument(3, String.class);
383                     String extension = iom.getArgument(4, String.class);
384                     String type = iom.getArgument(5, String.class);
385                     return session.getService(ArtifactFactory.class)
386                             .createProduced(ArtifactFactoryRequest.builder()
387                                     .session(session)
388                                     .groupId(groupId)
389                                     .artifactId(artifactId)
390                                     .version(version)
391                                     .classifier(classifier)
392                                     .extension(extension)
393                                     .type(type)
394                                     .build());
395                 });
396         when(session.createProducedArtifact(any(), any(), any(), any())).thenAnswer(iom -> {
397             String groupId = iom.getArgument(0, String.class);
398             String artifactId = iom.getArgument(1, String.class);
399             String version = iom.getArgument(2, String.class);
400             String extension = iom.getArgument(3, String.class);
401             return session.getService(ArtifactFactory.class)
402                     .createProduced(ArtifactFactoryRequest.builder()
403                             .session(session)
404                             .groupId(groupId)
405                             .artifactId(artifactId)
406                             .version(version)
407                             .extension(extension)
408                             .build());
409         });
410         when(session.getService(ArtifactFactory.class)).thenReturn(artifactFactory);
411 
412         //
413         // ProjectBuilder
414         //
415         ProjectBuilder projectBuilder = mock(ProjectBuilder.class);
416         when(projectBuilder.build(any(ProjectBuilderRequest.class))).then(iom -> {
417             ProjectBuilderRequest request = iom.getArgument(0, ProjectBuilderRequest.class);
418             ProjectBuilderResult result = mock(ProjectBuilderResult.class);
419             Model model =
420                     new MavenStaxReader().read(request.getSource().orElseThrow().openStream());
421             ProjectStub projectStub = new ProjectStub();
422             projectStub.setModel(model);
423             ProducedArtifactStub artifactStub = new ProducedArtifactStub(
424                     model.getGroupId(), model.getArtifactId(), "", model.getVersion(), model.getPackaging());
425             if (!"pom".equals(model.getPackaging())) {
426                 projectStub.setMainArtifact(artifactStub);
427             }
428             when(result.getProject()).thenReturn(Optional.of(projectStub));
429             return result;
430         });
431         when(session.getService(ProjectBuilder.class)).thenReturn(projectBuilder);
432 
433         //
434         // ModelXmlFactory
435         //
436         when(session.getService(ModelXmlFactory.class)).thenReturn(new DefaultModelXmlFactory());
437 
438         //
439         // Lookup
440         //
441         when(session.getService(Lookup.class)).thenReturn(LookupStub.EMPTY);
442 
443         //
444         // Other
445         //
446         Properties sysProps = new Properties();
447         Properties usrProps = new Properties();
448         doReturn(sysProps).when(session).getSystemProperties();
449         doReturn(usrProps).when(session).getUserProperties();
450         when(session.getLocalRepository()).thenReturn(localRepository);
451         when(session.getData()).thenReturn(new TestSessionData());
452         when(session.withLocalRepository(any()))
453                 .thenAnswer(iom -> getMockSession(iom.getArgument(0, LocalRepository.class)));
454 
455         return session;
456     }
457 
458     static String getPathForArtifact(Artifact artifact) {
459         StringBuilder path = new StringBuilder(128);
460         path.append(artifact.getGroupId().replace('.', '/')).append('/');
461         path.append(artifact.getArtifactId()).append('/');
462         path.append(artifact.getVersion()).append('/');
463         path.append(artifact.getArtifactId()).append('-');
464         path.append(artifact.getVersion());
465         if (!artifact.getClassifier().isEmpty()) {
466             path.append('-').append(artifact.getClassifier());
467         }
468         if (!artifact.getExtension().isEmpty()) {
469             path.append('.').append(artifact.getExtension());
470         }
471         return path.toString();
472     }
473 
474     static class TestSessionData implements SessionData {
475         private final Map<Key<?>, Object> map = new ConcurrentHashMap<>();
476 
477         @Override
478         public <T> void set(@Nonnull Key<T> key, T value) {
479             map.put(key, value);
480         }
481 
482         @Override
483         public <T> boolean replace(@Nonnull Key<T> key, T oldValue, T newValue) {
484             return map.replace(key, oldValue, newValue);
485         }
486 
487         @Override
488         @SuppressWarnings("unchecked")
489         public <T> T get(@Nonnull Key<T> key) {
490             return (T) map.get(key);
491         }
492 
493         @Override
494         @SuppressWarnings("unchecked")
495         public <T> T computeIfAbsent(@Nonnull Key<T> key, @Nonnull Supplier<T> supplier) {
496             return (T) map.computeIfAbsent(key, k -> supplier.get());
497         }
498     }
499 }