Add telnet interface for FreeDOS users
Telnet server with text-based menu for all Git operations. Enabled via webgit.telnet.enabled=true property, defaults to port 2323. Includes unit tests for session and server. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -15,4 +15,5 @@ public class WebgitProperties
|
||||
{
|
||||
private Path worktreePath;
|
||||
private Path gitDirPath;
|
||||
private Integer telnetPort;
|
||||
}
|
||||
|
||||
84
src/main/java/be/seeseepuff/webgit/telnet/TelnetServer.java
Normal file
84
src/main/java/be/seeseepuff/webgit/telnet/TelnetServer.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package be.seeseepuff.webgit.telnet;
|
||||
|
||||
import be.seeseepuff.webgit.config.WebgitProperties;
|
||||
import be.seeseepuff.webgit.service.GitService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "webgit.telnet.enabled", havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TelnetServer
|
||||
{
|
||||
private final GitService gitService;
|
||||
private final WebgitProperties properties;
|
||||
private ServerSocket serverSocket;
|
||||
private Thread acceptThread;
|
||||
|
||||
@PostConstruct
|
||||
public void start() throws IOException
|
||||
{
|
||||
int port = properties.getTelnetPort() != null ? properties.getTelnetPort() : 2323;
|
||||
serverSocket = new ServerSocket(port);
|
||||
log.info("Telnet server listening on port {}", port);
|
||||
|
||||
acceptThread = new Thread(this::acceptConnections, "telnet-accept");
|
||||
acceptThread.setDaemon(true);
|
||||
acceptThread.start();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void stop() throws IOException
|
||||
{
|
||||
if (serverSocket != null && !serverSocket.isClosed())
|
||||
{
|
||||
serverSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptConnections()
|
||||
{
|
||||
while (!serverSocket.isClosed())
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket socket = serverSocket.accept();
|
||||
log.info("Telnet connection from {}", socket.getRemoteSocketAddress());
|
||||
|
||||
Thread handler = new Thread(() -> handleConnection(socket), "telnet-client");
|
||||
handler.setDaemon(true);
|
||||
handler.start();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (!serverSocket.isClosed())
|
||||
{
|
||||
log.error("Error accepting telnet connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConnection(Socket socket)
|
||||
{
|
||||
try (socket;
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true))
|
||||
{
|
||||
new TelnetSession(gitService, in, out).run();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.error("Error handling telnet connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
320
src/main/java/be/seeseepuff/webgit/telnet/TelnetSession.java
Normal file
320
src/main/java/be/seeseepuff/webgit/telnet/TelnetSession.java
Normal file
@@ -0,0 +1,320 @@
|
||||
package be.seeseepuff.webgit.telnet;
|
||||
|
||||
import be.seeseepuff.webgit.service.GitService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class TelnetSession implements Runnable
|
||||
{
|
||||
private final GitService gitService;
|
||||
private final BufferedReader in;
|
||||
private final PrintWriter out;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
out.println("Welcome to WebGit Telnet Interface");
|
||||
out.println("==================================");
|
||||
mainMenu();
|
||||
}
|
||||
catch (IOException | GitAPIException e)
|
||||
{
|
||||
out.println("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void mainMenu() throws IOException, GitAPIException
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
out.println();
|
||||
out.println("Main Menu:");
|
||||
out.println(" 1. List repositories");
|
||||
out.println(" 2. Clone a repository");
|
||||
out.println(" 3. Open a repository");
|
||||
out.println(" q. Quit");
|
||||
out.print("> ");
|
||||
out.flush();
|
||||
|
||||
String choice = in.readLine();
|
||||
if (choice == null || "q".equalsIgnoreCase(choice))
|
||||
{
|
||||
out.println("Goodbye!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (choice.trim())
|
||||
{
|
||||
case "1" -> listRepositories();
|
||||
case "2" -> cloneRepository();
|
||||
case "3" -> openRepository();
|
||||
default -> out.println("Invalid choice.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listRepositories() throws IOException
|
||||
{
|
||||
List<String> repos = gitService.listRepositories();
|
||||
if (repos.isEmpty())
|
||||
{
|
||||
out.println("No repositories cloned yet.");
|
||||
}
|
||||
else
|
||||
{
|
||||
out.println("Repositories:");
|
||||
for (int i = 0; i < repos.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + repos.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cloneRepository() throws IOException, GitAPIException
|
||||
{
|
||||
out.print("URL: ");
|
||||
out.flush();
|
||||
String url = in.readLine();
|
||||
if (url == null || url.isBlank())
|
||||
return;
|
||||
|
||||
out.print("Name: ");
|
||||
out.flush();
|
||||
String name = in.readLine();
|
||||
if (name == null || name.isBlank())
|
||||
return;
|
||||
|
||||
gitService.cloneRepository(url.trim(), name.trim());
|
||||
out.println("Cloned successfully.");
|
||||
}
|
||||
|
||||
private void openRepository() throws IOException, GitAPIException
|
||||
{
|
||||
List<String> repos = gitService.listRepositories();
|
||||
if (repos.isEmpty())
|
||||
{
|
||||
out.println("No repositories cloned yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
out.println("Repositories:");
|
||||
for (int i = 0; i < repos.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + repos.get(i));
|
||||
}
|
||||
out.print("Enter number: ");
|
||||
out.flush();
|
||||
|
||||
String input = in.readLine();
|
||||
if (input == null || input.isBlank())
|
||||
return;
|
||||
|
||||
int index;
|
||||
try
|
||||
{
|
||||
index = Integer.parseInt(input.trim()) - 1;
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
out.println("Invalid number.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= repos.size())
|
||||
{
|
||||
out.println("Invalid selection.");
|
||||
return;
|
||||
}
|
||||
|
||||
repoMenu(repos.get(index));
|
||||
}
|
||||
|
||||
void repoMenu(String name) throws IOException, GitAPIException
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
out.println();
|
||||
out.println("Repository: " + name);
|
||||
out.println(" Branch: " + gitService.getCurrentBranch(name));
|
||||
out.println();
|
||||
out.println(" 1. List branches");
|
||||
out.println(" 2. Checkout branch");
|
||||
out.println(" 3. Create new branch");
|
||||
out.println(" 4. Show modified files");
|
||||
out.println(" 5. Show staged files");
|
||||
out.println(" 6. Stage files");
|
||||
out.println(" 7. Commit");
|
||||
out.println(" 8. Push");
|
||||
out.println(" 9. Pull");
|
||||
out.println(" b. Back");
|
||||
out.print("> ");
|
||||
out.flush();
|
||||
|
||||
String choice = in.readLine();
|
||||
if (choice == null || "b".equalsIgnoreCase(choice))
|
||||
return;
|
||||
|
||||
switch (choice.trim())
|
||||
{
|
||||
case "1" -> listBranches(name);
|
||||
case "2" -> checkoutBranch(name);
|
||||
case "3" -> createBranch(name);
|
||||
case "4" -> showModifiedFiles(name);
|
||||
case "5" -> showStagedFiles(name);
|
||||
case "6" -> stageFiles(name);
|
||||
case "7" -> commitChanges(name);
|
||||
case "8" -> push(name);
|
||||
case "9" -> pull(name);
|
||||
default -> out.println("Invalid choice.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listBranches(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> branches = gitService.listBranches(name);
|
||||
String current = gitService.getCurrentBranch(name);
|
||||
out.println("Branches:");
|
||||
for (String branch : branches)
|
||||
{
|
||||
out.println(branch.equals(current) ? " * " + branch : " " + branch);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkoutBranch(String name) throws IOException, GitAPIException
|
||||
{
|
||||
out.print("Branch name: ");
|
||||
out.flush();
|
||||
String branch = in.readLine();
|
||||
if (branch == null || branch.isBlank())
|
||||
return;
|
||||
gitService.checkoutBranch(name, branch.trim());
|
||||
out.println("Switched to " + branch.trim());
|
||||
}
|
||||
|
||||
private void createBranch(String name) throws IOException, GitAPIException
|
||||
{
|
||||
out.print("New branch name: ");
|
||||
out.flush();
|
||||
String branch = in.readLine();
|
||||
if (branch == null || branch.isBlank())
|
||||
return;
|
||||
gitService.createAndCheckoutBranch(name, branch.trim());
|
||||
out.println("Created and switched to " + branch.trim());
|
||||
}
|
||||
|
||||
private void showModifiedFiles(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> files = gitService.getModifiedFiles(name);
|
||||
if (files.isEmpty())
|
||||
{
|
||||
out.println("No modified files.");
|
||||
}
|
||||
else
|
||||
{
|
||||
out.println("Modified files:");
|
||||
for (int i = 0; i < files.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + files.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showStagedFiles(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> files = gitService.getStagedFiles(name);
|
||||
if (files.isEmpty())
|
||||
{
|
||||
out.println("No staged files.");
|
||||
}
|
||||
else
|
||||
{
|
||||
out.println("Staged files:");
|
||||
for (String file : files)
|
||||
{
|
||||
out.println(" " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stageFiles(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> files = gitService.getModifiedFiles(name);
|
||||
if (files.isEmpty())
|
||||
{
|
||||
out.println("No modified files to stage.");
|
||||
return;
|
||||
}
|
||||
|
||||
out.println("Modified files:");
|
||||
for (int i = 0; i < files.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + files.get(i));
|
||||
}
|
||||
out.print("Enter numbers to stage (comma-separated, or 'a' for all): ");
|
||||
out.flush();
|
||||
|
||||
String input = in.readLine();
|
||||
if (input == null || input.isBlank())
|
||||
return;
|
||||
|
||||
List<String> toStage;
|
||||
if ("a".equalsIgnoreCase(input.trim()))
|
||||
{
|
||||
toStage = files;
|
||||
}
|
||||
else
|
||||
{
|
||||
toStage = new java.util.ArrayList<>();
|
||||
for (String part : input.split(","))
|
||||
{
|
||||
try
|
||||
{
|
||||
int idx = Integer.parseInt(part.trim()) - 1;
|
||||
if (idx >= 0 && idx < files.size())
|
||||
toStage.add(files.get(idx));
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!toStage.isEmpty())
|
||||
{
|
||||
gitService.stageFiles(name, toStage);
|
||||
out.println("Staged " + toStage.size() + " file(s).");
|
||||
}
|
||||
}
|
||||
|
||||
private void commitChanges(String name) throws IOException, GitAPIException
|
||||
{
|
||||
out.print("Commit message: ");
|
||||
out.flush();
|
||||
String message = in.readLine();
|
||||
if (message == null || message.isBlank())
|
||||
return;
|
||||
gitService.commit(name, message.trim());
|
||||
out.println("Committed.");
|
||||
}
|
||||
|
||||
private void push(String name) throws IOException, GitAPIException
|
||||
{
|
||||
gitService.push(name);
|
||||
out.println("Pushed.");
|
||||
}
|
||||
|
||||
private void pull(String name) throws IOException, GitAPIException
|
||||
{
|
||||
gitService.pull(name);
|
||||
out.println("Pulled.");
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,8 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
@SpringBootTest
|
||||
@TestPropertySource(properties = {
|
||||
"webgit.worktree-path=/mnt/shared/repos",
|
||||
"webgit.git-dir-path=/var/lib/webgit/git"
|
||||
"webgit.git-dir-path=/var/lib/webgit/git",
|
||||
"webgit.telnet-port=2323"
|
||||
})
|
||||
class WebgitPropertiesTest
|
||||
{
|
||||
@@ -30,4 +31,10 @@ class WebgitPropertiesTest
|
||||
{
|
||||
assertEquals(Path.of("/var/lib/webgit/git"), properties.getGitDirPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void telnetPortIsBound()
|
||||
{
|
||||
assertEquals(2323, properties.getTelnetPort());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package be.seeseepuff.webgit.telnet;
|
||||
|
||||
import be.seeseepuff.webgit.config.WebgitProperties;
|
||||
import be.seeseepuff.webgit.service.GitService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TelnetServerTest
|
||||
{
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void serverAcceptsConnectionAndResponds() throws Exception
|
||||
{
|
||||
WebgitProperties props = new WebgitProperties();
|
||||
props.setWorktreePath(tempDir.resolve("worktrees"));
|
||||
props.setGitDirPath(tempDir.resolve("gitdirs"));
|
||||
props.setTelnetPort(0); // use any free port
|
||||
|
||||
GitService gitService = new GitService(props);
|
||||
|
||||
// We'll test with a real server socket but use port 0 for a random free port
|
||||
// Since the TelnetServer uses PostConstruct, we'll test the component manually
|
||||
var serverSocket = new java.net.ServerSocket(0);
|
||||
int port = serverSocket.getLocalPort();
|
||||
serverSocket.close();
|
||||
|
||||
props.setTelnetPort(port);
|
||||
TelnetServer server = new TelnetServer(gitService, props);
|
||||
server.start();
|
||||
|
||||
try (Socket socket = new Socket("localhost", port);
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true))
|
||||
{
|
||||
// Read the welcome message
|
||||
String line = in.readLine();
|
||||
assertTrue(line.contains("Welcome to WebGit"));
|
||||
|
||||
// Send quit
|
||||
out.println("q");
|
||||
|
||||
// Read until we see Goodbye
|
||||
String response = "";
|
||||
String l;
|
||||
while ((l = in.readLine()) != null)
|
||||
{
|
||||
response += l + "\n";
|
||||
}
|
||||
assertTrue(response.contains("Goodbye!"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
352
src/test/java/be/seeseepuff/webgit/telnet/TelnetSessionTest.java
Normal file
352
src/test/java/be/seeseepuff/webgit/telnet/TelnetSessionTest.java
Normal file
@@ -0,0 +1,352 @@
|
||||
package be.seeseepuff.webgit.telnet;
|
||||
|
||||
import be.seeseepuff.webgit.service.GitService;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TelnetSessionTest
|
||||
{
|
||||
@Mock
|
||||
private GitService gitService;
|
||||
|
||||
private TelnetSession createSession(String input)
|
||||
{
|
||||
BufferedReader in = new BufferedReader(new StringReader(input));
|
||||
PrintWriter out = new PrintWriter(new StringWriter());
|
||||
return new TelnetSession(gitService, in, out);
|
||||
}
|
||||
|
||||
private String runSession(String input)
|
||||
{
|
||||
BufferedReader in = new BufferedReader(new StringReader(input));
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter out = new PrintWriter(sw, true);
|
||||
new TelnetSession(gitService, in, out).run();
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
void quitExitsImmediately()
|
||||
{
|
||||
String output = runSession("q\n");
|
||||
assertTrue(output.contains("Welcome to WebGit"));
|
||||
assertTrue(output.contains("Goodbye!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullInputExits()
|
||||
{
|
||||
String output = runSession("");
|
||||
assertTrue(output.contains("Welcome to WebGit"));
|
||||
assertTrue(output.contains("Goodbye!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRepositoriesShowsEmpty() throws IOException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of());
|
||||
String output = runSession("1\nq\n");
|
||||
assertTrue(output.contains("No repositories cloned yet."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRepositoriesShowsRepos() throws IOException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("alpha", "beta"));
|
||||
String output = runSession("1\nq\n");
|
||||
assertTrue(output.contains("alpha"));
|
||||
assertTrue(output.contains("beta"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cloneRepository() throws IOException, GitAPIException
|
||||
{
|
||||
String output = runSession("2\nhttps://example.com/repo.git\nmyrepo\nq\n");
|
||||
assertTrue(output.contains("Cloned successfully."));
|
||||
verify(gitService).cloneRepository("https://example.com/repo.git", "myrepo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void cloneRepositoryEmptyUrl()
|
||||
{
|
||||
String output = runSession("2\n\nq\n");
|
||||
assertFalse(output.contains("Cloned successfully."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cloneRepositoryEmptyName()
|
||||
{
|
||||
String output = runSession("2\nhttps://example.com/repo.git\n\nq\n");
|
||||
assertFalse(output.contains("Cloned successfully."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openRepositoryEmpty() throws IOException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of());
|
||||
String output = runSession("3\nq\n");
|
||||
assertTrue(output.contains("No repositories cloned yet."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openRepositoryInvalidNumber() throws IOException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
String output = runSession("3\nabc\nq\n");
|
||||
assertTrue(output.contains("Invalid number."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openRepositoryOutOfRange() throws IOException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
String output = runSession("3\n5\nq\n");
|
||||
assertTrue(output.contains("Invalid selection."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openRepositoryAndGoBack() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\nb\nq\n");
|
||||
assertTrue(output.contains("Repository: myrepo"));
|
||||
assertTrue(output.contains("Branch: main"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuListBranches() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.listBranches("myrepo")).thenReturn(List.of("main", "develop"));
|
||||
String output = runSession("3\n1\n1\nb\nq\n");
|
||||
assertTrue(output.contains("* main"));
|
||||
assertTrue(output.contains("develop"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuCheckoutBranch() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n2\ndevelop\nb\nq\n");
|
||||
verify(gitService).checkoutBranch("myrepo", "develop");
|
||||
assertTrue(output.contains("Switched to develop"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuCheckoutBranchEmpty() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n2\n\nb\nq\n");
|
||||
verify(gitService, never()).checkoutBranch(anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuCreateBranch() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n3\nfeature-x\nb\nq\n");
|
||||
verify(gitService).createAndCheckoutBranch("myrepo", "feature-x");
|
||||
assertTrue(output.contains("Created and switched to feature-x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuCreateBranchEmpty() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n3\n\nb\nq\n");
|
||||
verify(gitService, never()).createAndCheckoutBranch(anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuShowModifiedFilesEmpty() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getModifiedFiles("myrepo")).thenReturn(List.of());
|
||||
String output = runSession("3\n1\n4\nb\nq\n");
|
||||
assertTrue(output.contains("No modified files."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuShowModifiedFiles() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getModifiedFiles("myrepo")).thenReturn(List.of("a.txt", "b.txt"));
|
||||
String output = runSession("3\n1\n4\nb\nq\n");
|
||||
assertTrue(output.contains("a.txt"));
|
||||
assertTrue(output.contains("b.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuShowStagedFilesEmpty() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getStagedFiles("myrepo")).thenReturn(List.of());
|
||||
String output = runSession("3\n1\n5\nb\nq\n");
|
||||
assertTrue(output.contains("No staged files."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuShowStagedFiles() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getStagedFiles("myrepo")).thenReturn(List.of("staged.txt"));
|
||||
String output = runSession("3\n1\n5\nb\nq\n");
|
||||
assertTrue(output.contains("staged.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuStageAllFiles() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getModifiedFiles("myrepo")).thenReturn(List.of("a.txt", "b.txt"));
|
||||
String output = runSession("3\n1\n6\na\nb\nq\n");
|
||||
verify(gitService).stageFiles("myrepo", List.of("a.txt", "b.txt"));
|
||||
assertTrue(output.contains("Staged 2 file(s)."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuStageSelectedFiles() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getModifiedFiles("myrepo")).thenReturn(List.of("a.txt", "b.txt", "c.txt"));
|
||||
String output = runSession("3\n1\n6\n1,3\nb\nq\n");
|
||||
verify(gitService).stageFiles("myrepo", List.of("a.txt", "c.txt"));
|
||||
assertTrue(output.contains("Staged 2 file(s)."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuStageNoModifiedFiles() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getModifiedFiles("myrepo")).thenReturn(List.of());
|
||||
String output = runSession("3\n1\n6\nb\nq\n");
|
||||
assertTrue(output.contains("No modified files to stage."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuStageEmptyInput() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getModifiedFiles("myrepo")).thenReturn(List.of("a.txt"));
|
||||
String output = runSession("3\n1\n6\n\nb\nq\n");
|
||||
verify(gitService, never()).stageFiles(anyString(), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuCommit() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n7\nmy commit msg\nb\nq\n");
|
||||
verify(gitService).commit("myrepo", "my commit msg");
|
||||
assertTrue(output.contains("Committed."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuCommitEmpty() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n7\n\nb\nq\n");
|
||||
verify(gitService, never()).commit(anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuPush() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n8\nb\nq\n");
|
||||
verify(gitService).push("myrepo");
|
||||
assertTrue(output.contains("Pushed."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuPull() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\n9\nb\nq\n");
|
||||
verify(gitService).pull("myrepo");
|
||||
assertTrue(output.contains("Pulled."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidMainMenuChoice()
|
||||
{
|
||||
String output = runSession("x\nq\n");
|
||||
assertTrue(output.contains("Invalid choice."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidRepoMenuChoice() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
String output = runSession("3\n1\nx\nb\nq\n");
|
||||
assertTrue(output.contains("Invalid choice."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repoMenuNullInputExits() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
// After entering repo menu, the input stream ends (null)
|
||||
String output = runSession("3\n1\n");
|
||||
assertTrue(output.contains("Repository: myrepo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openRepositoryEmptyInput() throws IOException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
String output = runSession("3\n\nq\n");
|
||||
// Empty input returns to main menu
|
||||
assertFalse(output.contains("Repository: myrepo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openRepositoryZeroIndex() throws IOException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
String output = runSession("3\n0\nq\n");
|
||||
assertTrue(output.contains("Invalid selection."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stageInvalidNumbers() throws IOException, GitAPIException
|
||||
{
|
||||
when(gitService.listRepositories()).thenReturn(List.of("myrepo"));
|
||||
when(gitService.getCurrentBranch("myrepo")).thenReturn("main");
|
||||
when(gitService.getModifiedFiles("myrepo")).thenReturn(List.of("a.txt"));
|
||||
// "abc" is not a valid number - no files staged
|
||||
String output = runSession("3\n1\n6\nabc\nb\nq\n");
|
||||
verify(gitService, never()).stageFiles(anyString(), anyList());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user