Skip to content

Commit 67473b7

Browse files
committed
[MNG-8425] Fix mvnenc init saving invalid master source configuration
Reimplemented the fix inline and added an API test using Mockito to mock the interactive ConsolePrompt, directly addressing reviewer feedback.
1 parent 6f46443 commit 67473b7

2 files changed

Lines changed: 145 additions & 1 deletion

File tree

  • impl/maven-cli/src
    • main/java/org/apache/maven/cling/invoker/mvnenc/goals
    • test/java/org/apache/maven/cling/invoker/mvnenc/goals

impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,20 @@ public int doExecute(EncryptContext context) throws Exception {
143143
if (editMap.isEmpty()) {
144144
throw new InterruptedException();
145145
}
146-
dispatcherConfigResult.put(editable.getKey(), editMap.get("edit"));
146+
String userInput = editMap.get("edit").getResult();
147+
if (template.contains("$")) {
148+
String prefix = template.substring(0, template.indexOf('$'));
149+
if (!prefix.isEmpty() && !userInput.startsWith(prefix)) {
150+
userInput = prefix + userInput;
151+
}
152+
}
153+
final String finalResult = userInput;
154+
dispatcherConfigResult.put(editable.getKey(), new PromptResultItemIF() {
155+
@Override
156+
public String getResult() {
157+
return finalResult;
158+
}
159+
});
147160
}
148161
}
149162

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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.cling.invoker.mvnenc.goals;
20+
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
26+
import org.apache.maven.api.cli.InvokerRequest;
27+
import org.apache.maven.api.cli.mvnenc.EncryptOptions;
28+
import org.apache.maven.api.services.MessageBuilderFactory;
29+
import org.apache.maven.cling.invoker.mvnenc.EncryptContext;
30+
import org.codehaus.plexus.components.secdispatcher.DispatcherMeta;
31+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
32+
import org.codehaus.plexus.components.secdispatcher.model.Config;
33+
import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty;
34+
import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;
35+
import org.jline.consoleui.prompt.ConsolePrompt;
36+
import org.jline.consoleui.prompt.PromptResultItemIF;
37+
import org.jline.consoleui.prompt.builder.PromptBuilder;
38+
import org.junit.jupiter.api.Test;
39+
import org.mockito.MockedConstruction;
40+
import org.mockito.Mockito;
41+
42+
import static org.junit.jupiter.api.Assertions.assertEquals;
43+
import static org.mockito.ArgumentMatchers.any;
44+
import static org.mockito.Mockito.mock;
45+
import static org.mockito.Mockito.when;
46+
47+
class InitTest {
48+
49+
@Test
50+
void testPrefixPrependedToUserInput() throws Exception {
51+
MessageBuilderFactory messageBuilderFactory = mock(MessageBuilderFactory.class, Mockito.RETURNS_DEEP_STUBS);
52+
SecDispatcher secDispatcher = mock(SecDispatcher.class);
53+
54+
SettingsSecurity settingsSecurity = new SettingsSecurity();
55+
when(secDispatcher.readConfiguration(true)).thenReturn(settingsSecurity);
56+
57+
DispatcherMeta meta = mock(DispatcherMeta.class);
58+
when(meta.name()).thenReturn("master");
59+
DispatcherMeta.Field field = mock(DispatcherMeta.Field.class);
60+
when(field.getKey()).thenReturn("password");
61+
when(meta.fields()).thenReturn(Collections.singletonList(field));
62+
63+
when(secDispatcher.availableDispatchers()).thenReturn(Collections.singleton(meta));
64+
65+
Init init = new Init(messageBuilderFactory, secDispatcher);
66+
67+
InvokerRequest invokerRequest = mock(InvokerRequest.class, Mockito.RETURNS_DEEP_STUBS);
68+
when(invokerRequest.cwd()).thenReturn(java.nio.file.Paths.get(""));
69+
when(invokerRequest.installationDirectory()).thenReturn(java.nio.file.Paths.get(""));
70+
when(invokerRequest.userHomeDirectory()).thenReturn(java.nio.file.Paths.get(""));
71+
when(invokerRequest.topDirectory()).thenReturn(java.nio.file.Paths.get(""));
72+
when(invokerRequest.rootDirectory()).thenReturn(java.util.Optional.empty());
73+
EncryptOptions options = mock(EncryptOptions.class);
74+
EncryptContext context = new EncryptContext(invokerRequest, options);
75+
// avoid null pointers for lists
76+
context.header = new java.util.ArrayList<>();
77+
context.style = new org.jline.utils.AttributedStyle();
78+
org.jline.terminal.Terminal terminal = mock(org.jline.terminal.Terminal.class);
79+
java.io.PrintWriter printWriter = mock(java.io.PrintWriter.class);
80+
when(terminal.writer()).thenReturn(printWriter);
81+
context.terminal = terminal;
82+
83+
try (MockedConstruction<ConsolePrompt> mockedPrompt =
84+
Mockito.mockConstruction(ConsolePrompt.class, (mock, ctx) -> {
85+
PromptBuilder builderMock = mock(PromptBuilder.class, Mockito.RETURNS_DEEP_STUBS);
86+
when(mock.getPromptBuilder()).thenReturn(builderMock);
87+
88+
Map<String, PromptResultItemIF> dispatcherResult = new HashMap<>();
89+
dispatcherResult.put("defaultDispatcher", createResult("master"));
90+
91+
Map<String, PromptResultItemIF> configureResult = new HashMap<>();
92+
configureResult.put("password", createResult("env:$MVN_PASSWORD"));
93+
94+
Map<String, PromptResultItemIF> editResult = new HashMap<>();
95+
editResult.put(
96+
"edit", createResult("my_password_var")); // User inputs "my_password_var" without prefix
97+
98+
Map<String, PromptResultItemIF> confirmResult = new HashMap<>();
99+
org.jline.consoleui.prompt.ConfirmResult confirm =
100+
mock(org.jline.consoleui.prompt.ConfirmResult.class);
101+
when(confirm.getConfirmed())
102+
.thenReturn(org.jline.consoleui.elements.ConfirmChoice.ConfirmationValue.YES);
103+
confirmResult.put("confirm", confirm);
104+
105+
when(mock.prompt(any(java.util.List.class), any(java.util.List.class)))
106+
.thenReturn(dispatcherResult, configureResult, editResult, confirmResult);
107+
})) {
108+
init.doExecute(context);
109+
}
110+
111+
// Validate that the SettingsSecurity model has the prepended prefix
112+
List<Config> configs = settingsSecurity.getConfigurations();
113+
assertEquals(1, configs.size());
114+
Config config = configs.get(0);
115+
assertEquals("master", config.getName());
116+
assertEquals(1, config.getProperties().size());
117+
ConfigProperty prop = config.getProperties().get(0);
118+
assertEquals("password", prop.getName());
119+
// The expected value should be env:my_password_var because the template was env:$MVN_PASSWORD
120+
assertEquals("env:my_password_var", prop.getValue());
121+
}
122+
123+
private PromptResultItemIF createResult(String value) {
124+
return new PromptResultItemIF() {
125+
@Override
126+
public String getResult() {
127+
return value;
128+
}
129+
};
130+
}
131+
}

0 commit comments

Comments
 (0)