Skip to content

Commit 7c4cde4

Browse files
committed
Add input support to ShellTestClient for interactive command testing
Signed-off-by: David Pilar <david@czpilar.net>
1 parent d714902 commit 7c4cde4

File tree

3 files changed

+162
-7
lines changed

3 files changed

+162
-7
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.springframework.shell.test;
2+
3+
import java.util.ArrayList;
4+
import java.util.Deque;
5+
import java.util.LinkedList;
6+
import java.util.List;
7+
8+
/**
9+
* Represents a provider for inputs and passwords used in shell interactions. This class
10+
* uses two {@link Deque} instances to manage the queue of user inputs and passwords. It
11+
* serves as a utility for simulating user input during testing or automated shell
12+
* interactions.
13+
*
14+
* @param inputs Queue containing user input strings.
15+
* @param passwords Queue containing user password strings.
16+
*/
17+
public record ShellInputProvider(Deque<String> inputs, Deque<String> passwords) {
18+
19+
static Builder builder() {
20+
return Builder.builder();
21+
}
22+
23+
final static class Builder {
24+
25+
private final List<String> inputs = new ArrayList<>();
26+
27+
private final List<String> passwords = new ArrayList<>();
28+
29+
static Builder builder() {
30+
return new Builder();
31+
}
32+
33+
public Builder input(String... input) {
34+
inputs.addAll(List.of(input));
35+
return this;
36+
}
37+
38+
public Builder password(String... password) {
39+
passwords.addAll(List.of(password));
40+
return this;
41+
}
42+
43+
public ShellInputProvider build() {
44+
return new ShellInputProvider(new LinkedList<>(inputs), new LinkedList<>(passwords));
45+
}
46+
47+
}
48+
}

spring-shell-test/src/main/java/org/springframework/shell/test/ShellTestClient.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,20 @@
1515
*/
1616
package org.springframework.shell.test;
1717

18+
import org.springframework.shell.core.InputReader;
19+
import org.springframework.shell.core.command.*;
20+
1821
import java.io.PrintWriter;
1922
import java.io.StringWriter;
2023

21-
import org.springframework.shell.core.InputReader;
22-
import org.springframework.shell.core.command.CommandContext;
23-
import org.springframework.shell.core.command.CommandExecutor;
24-
import org.springframework.shell.core.command.CommandParser;
25-
import org.springframework.shell.core.command.CommandRegistry;
26-
import org.springframework.shell.core.command.ParsedInput;
27-
2824
/**
2925
* Client for shell session which can be used as a programmatic way to interact with a
3026
* shell application. In a typical test, it is required to send a command to the shell as
3127
* if a user typed it and then verify the shell output.
3228
*
3329
* @author Janne Valkealahti
3430
* @author Mahmoud Ben Hassine
31+
* @author David Pilar
3532
*/
3633
public class ShellTestClient {
3734

@@ -60,10 +57,36 @@ public ShellTestClient(CommandParser commandParser, CommandRegistry commandRegis
6057
* @throws Exception if an error occurred during command execution
6158
*/
6259
public ShellScreen sendCommand(String input) throws Exception {
60+
return sendCommand(input, ShellInputProvider.builder().build());
61+
}
62+
63+
/**
64+
* Sends a command to the shell, processes user inputs or passwords as needed, and
65+
* returns the resulting shell screen output.
66+
* @param input the raw command string to be sent to the shell
67+
* @param inputProvider a provider containing simulated user inputs and passwords
68+
* @return a {@code ShellScreen} object containing the output of the shell after
69+
* command execution
70+
* @throws Exception if an error occurs during the command execution
71+
*/
72+
public ShellScreen sendCommand(String input, ShellInputProvider inputProvider) throws Exception {
6373
StringWriter stringWriter = new StringWriter();
6474
ParsedInput parsedInput = this.commandParser.parse(input);
6575
PrintWriter outputWriter = new PrintWriter(stringWriter);
6676
InputReader inputReader = new InputReader() {
77+
@Override
78+
public String readInput(String prompt) {
79+
outputWriter.print(prompt);
80+
String in = inputProvider.inputs().pollFirst();
81+
return in == null ? "" : in;
82+
}
83+
84+
@Override
85+
public char[] readPassword(String prompt) {
86+
outputWriter.print(prompt);
87+
String pwd = inputProvider.passwords().pollFirst();
88+
return pwd == null ? new char[] {} : pwd.toCharArray();
89+
}
6790
};
6891
CommandContext commandContext = new CommandContext(parsedInput, this.commandRegistry, outputWriter,
6992
inputReader);

spring-shell-test/src/test/java/org/springframework/shell/test/ShellTestClientTests.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import org.springframework.shell.core.command.ExitStatus;
1818
import org.springframework.test.context.junit.jupiter.SpringExtension;
1919

20+
import java.io.PrintWriter;
21+
2022
@ExtendWith(SpringExtension.class)
2123
class ShellTestClientTests {
2224

@@ -36,6 +38,42 @@ void testUnknownCommandExecution(@Autowired ShellTestClient shellTestClient) {
3638
.isInstanceOf(CommandNotFoundException.class);
3739
}
3840

41+
@Test
42+
void testCommandExecutionWithReadingInput(@Autowired ShellTestClient client) throws Exception {
43+
// when
44+
ShellScreen screen = client.sendCommand("hello", ShellInputProvider.builder().input("hi").build());
45+
46+
// then
47+
ShellAssertions.assertThat(screen).containsText("You said: hi");
48+
}
49+
50+
@Test
51+
void testCommandExecutionWithReadingPassword(@Autowired ShellTestClient client) throws Exception {
52+
// when
53+
ShellScreen screen = client.sendCommand("password", ShellInputProvider.builder().password("secret123").build());
54+
55+
// then
56+
ShellAssertions.assertThat(screen).containsText("Your password is: secret123");
57+
}
58+
59+
@Test
60+
void testCommandExecutionWithComplexInputs(@Autowired ShellTestClient client) throws Exception {
61+
// given
62+
ShellInputProvider inputProvider = ShellInputProvider.builder()
63+
.input("One", "Two")
64+
.password("secret1", "secret2")
65+
.build();
66+
67+
// when
68+
ShellScreen screen = client.sendCommand("complex", inputProvider);
69+
70+
// then
71+
ShellAssertions.assertThat(screen).containsText("First input is: One");
72+
ShellAssertions.assertThat(screen).containsText("First password is: secret1");
73+
ShellAssertions.assertThat(screen).containsText("Second input is: Two");
74+
ShellAssertions.assertThat(screen).containsText("Second password is: secret2");
75+
}
76+
3977
@Configuration
4078
static class TestCommands {
4179

@@ -50,6 +88,52 @@ public ExitStatus doExecute(CommandContext commandContext) {
5088
};
5189
}
5290

91+
@Bean
92+
public Command hello() {
93+
return new AbstractCommand("hello", "A hello command") {
94+
@Override
95+
public ExitStatus doExecute(CommandContext commandContext) throws Exception {
96+
String message = commandContext.inputReader().readInput();
97+
commandContext.outputWriter().println("You said: " + message);
98+
return ExitStatus.OK;
99+
}
100+
};
101+
}
102+
103+
@Bean
104+
public Command password() {
105+
return new AbstractCommand("password", "A password command") {
106+
@Override
107+
public ExitStatus doExecute(CommandContext commandContext) throws Exception {
108+
char[] chars = commandContext.inputReader().readPassword();
109+
commandContext.outputWriter().println("Your password is: " + new String(chars));
110+
return ExitStatus.OK;
111+
}
112+
};
113+
}
114+
115+
@Bean
116+
public Command complexCommand() {
117+
return new AbstractCommand("complex", "A complex command") {
118+
@Override
119+
public ExitStatus doExecute(CommandContext commandContext) throws Exception {
120+
String message = commandContext.inputReader().readInput();
121+
commandContext.outputWriter().println("First input is: " + message);
122+
123+
char[] chars = commandContext.inputReader().readPassword();
124+
commandContext.outputWriter().println("First password is: " + new String(chars));
125+
126+
message = commandContext.inputReader().readInput();
127+
commandContext.outputWriter().println("Second input is: " + message);
128+
129+
chars = commandContext.inputReader().readPassword();
130+
commandContext.outputWriter().println("Second password is: " + new String(chars));
131+
132+
return ExitStatus.OK;
133+
}
134+
};
135+
}
136+
53137
@Bean
54138
public CommandRegistry commandRegistry() {
55139
return new CommandRegistry();

0 commit comments

Comments
 (0)