/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ package com.github.copilot.sdk; import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.net.ServerSocket; import java.net.URI; import org.junit.jupiter.api.Test; import com.github.copilot.sdk.json.CopilotClientOptions; import com.github.copilot.sdk.json.TelemetryConfig; /** * Unit tests for {@link CliServerManager} covering parseCliUrl, * connectToServer, resolveCliCommand, and ProcessInfo coverage gaps identified * by JaCoCo. */ class CliServerManagerTest { // ===== parseCliUrl tests ===== @Test void parseCliUrlWithPortNumber() { URI uri = CliServerManager.parseCliUrl("8080"); assertEquals("http://localhost:8080", uri.toString()); } @Test void parseCliUrlWithHostColonPort() { URI uri = CliServerManager.parseCliUrl("myhost:9090"); assertEquals("https://myhost:9090", uri.toString()); } @Test void parseCliUrlWithHttpPrefix() { URI uri = CliServerManager.parseCliUrl("http://example.com:3000"); assertEquals("http://example.com:3000", uri.toString()); } @Test void parseCliUrlWithHttpsPrefix() { URI uri = CliServerManager.parseCliUrl("https://secure.host:443"); assertEquals("https://secure.host:443", uri.toString()); } @Test void parseCliUrlWithHostOnly() { URI uri = CliServerManager.parseCliUrl("copilot.example.com"); assertEquals("https://copilot.example.com", uri.toString()); } // ===== connectToServer tests ===== @Test void connectToServerTcpMode() throws Exception { var options = new CopilotClientOptions(); var manager = new CliServerManager(options); // Start a temporary server socket to connect to try (ServerSocket ss = new ServerSocket(0)) { int port = ss.getLocalPort(); JsonRpcClient client = manager.connectToServer(null, "localhost", port); assertNotNull(client); client.close(); } } private static Process startBlockingProcess() throws IOException { boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); return (isWindows ? new ProcessBuilder("cmd", "/c", "more") : new ProcessBuilder("cat")).start(); } @Test void connectToServerStdioMode() throws Exception { var options = new CopilotClientOptions(); var manager = new CliServerManager(options); // Create a dummy process for stdio mode Process process = startBlockingProcess(); try { JsonRpcClient client = manager.connectToServer(process, null, null); assertNotNull(client); client.close(); } finally { process.destroyForcibly(); } } @Test void connectToServerNoProcessNoHost() { var options = new CopilotClientOptions(); var manager = new CliServerManager(options); var ex = assertThrows(IllegalStateException.class, () -> manager.connectToServer(null, null, null)); assertTrue(ex.getMessage().contains("Cannot connect")); } @Test void connectToServerNullHostNonNullPort() { var options = new CopilotClientOptions(); var manager = new CliServerManager(options); // tcpHost is null but tcpPort is non-null → falls to process check → process // null → exception var ex = assertThrows(IllegalStateException.class, () -> manager.connectToServer(null, null, 8080)); assertTrue(ex.getMessage().contains("Cannot connect")); } // ===== ProcessInfo record tests ===== @Test void processInfoRecord() { var info = new CliServerManager.ProcessInfo(null, 12345); assertNull(info.process()); assertEquals(12345, info.port()); } @Test void processInfoWithNullPort() { var info = new CliServerManager.ProcessInfo(null, null); assertNull(info.process()); assertNull(info.port()); } // ===== resolveCliCommand tests (via startCliServer) ===== // resolveCliCommand is private, so we test indirectly through startCliServer // with specific cliPath values. // On Windows, "/nonexistent/copilot" is not an absolute path (no drive letter), // so resolveCliCommand wraps it with "cmd /c" and ProcessBuilder.start() // succeeds // (launching cmd.exe). Use a Windows-absolute path to ensure IOException. private static final String NONEXISTENT_CLI = System.getProperty("os.name").toLowerCase().contains("win") ? "C:\\nonexistent\\copilot" : "/nonexistent/copilot"; @Test void startCliServerWithJsFile() throws Exception { // Using a .js file path causes resolveCliCommand to prepend "node" // node is on PATH so the process starts, but the script doesn't exist // so node exits quickly — verifying the .js branch was taken var options = new CopilotClientOptions().setCliPath("/nonexistent/script.js").setUseStdio(true); var manager = new CliServerManager(options); try { var info = manager.startCliServer(); // If process started, clean it up info.process().destroyForcibly(); } catch (IOException e) { // Expected — node may fail or not be present; either way the branch is hit assertNotNull(e); } } @Test void startCliServerWithCliArgs() throws Exception { // Test that cliArgs are included in the command var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setCliArgs(new String[]{"--extra-flag"}) .setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); assertNotNull(ex); } @Test void startCliServerWithExplicitPort() throws Exception { // Test the explicit port branch (useStdio=false, port > 0) var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setUseStdio(false).setPort(9999); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); assertNotNull(ex); } @Test void startCliServerWithGitHubToken() throws Exception { // Test the github token branch var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setGitHubToken("ghp_test123") .setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); assertNotNull(ex); } @Test void startCliServerWithUseLoggedInUserExplicit() throws Exception { // Test the explicit useLoggedInUser=false branch (adds --no-auto-login) var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setUseLoggedInUser(false) .setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); assertNotNull(ex); } @Test void startCliServerWithGitHubTokenAndNoExplicitUseLoggedInUser() throws Exception { // When gitHubToken is set and useLoggedInUser is null, defaults to false var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setGitHubToken("ghp_test123") .setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); assertNotNull(ex); } @Test void startCliServerWithNullCliPath() throws Exception { // Test the default cliPath branch (defaults to "copilot" when not set) var options = new CopilotClientOptions().setUseStdio(true); var manager = new CliServerManager(options); // "copilot" likely doesn't exist in the test env — that's fine try { var info = manager.startCliServer(); info.process().destroyForcibly(); } catch (IOException e) { // Expected if "copilot" is not on PATH assertNotNull(e); } } @Test void startCliServerWithTelemetryAllOptions() throws Exception { // The telemetry env vars are applied before ProcessBuilder.start() // so even with a nonexistent CLI path, the telemetry code path is exercised var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setFilePath("/tmp/telemetry.log") .setExporterType("otlp-http").setSourceName("test-app").setCaptureContent(true); var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); assertNotNull(ex); } @Test void startCliServerWithTelemetryCaptureContentFalse() throws Exception { // Test the false branch of getCaptureContent() var telemetry = new TelemetryConfig().setCaptureContent(false); var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); assertNotNull(ex); } }