Skip to content

Commit bd38cec

Browse files
committed
test: add coverage for CopilotClient state management and connection handling
1 parent 9d7c279 commit bd38cec

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

src/test/java/com/github/copilot/sdk/CopilotClientTest.java

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.nio.file.Path;
1919
import java.nio.file.Paths;
2020
import java.util.ArrayList;
21+
import java.util.concurrent.CompletableFuture;
22+
import java.util.concurrent.ExecutionException;
2123

2224
import static org.junit.jupiter.api.Assertions.*;
2325

@@ -323,4 +325,128 @@ void testOnLifecycleMultipleHandlers() throws Exception {
323325
assertEquals(1, typed.size());
324326
}
325327
}
328+
329+
// ===== getState() coverage =====
330+
331+
@Test
332+
void testGetStateErrorAfterFailedStart() throws Exception {
333+
// Use a non-existent CLI path to trigger a startup failure
334+
var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false);
335+
336+
try (var client = new CopilotClient(options)) {
337+
// Manually start to trigger the error
338+
CompletableFuture<Void> startFuture = client.start();
339+
340+
// Wait for the start to fail
341+
try {
342+
startFuture.get();
343+
} catch (ExecutionException e) {
344+
// Expected
345+
}
346+
347+
assertEquals(ConnectionState.ERROR, client.getState());
348+
}
349+
}
350+
351+
@Test
352+
void testGetStateConnectingDuringStart() throws Exception {
353+
// Use a non-existent CLI path; the future won't complete immediately
354+
var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false);
355+
356+
try (var client = new CopilotClient(options)) {
357+
// Start is async - grab state before completion
358+
client.start();
359+
360+
// The state should be either CONNECTING or ERROR depending on timing
361+
ConnectionState state = client.getState();
362+
assertTrue(state == ConnectionState.CONNECTING || state == ConnectionState.ERROR,
363+
"State should be CONNECTING or ERROR, was: " + state);
364+
}
365+
}
366+
367+
// ===== ensureConnected throws when autoStart=false and not connected =====
368+
369+
@Test
370+
void testEnsureConnectedThrowsWhenNotStartedAndAutoStartDisabled() {
371+
var options = new CopilotClientOptions().setAutoStart(false);
372+
373+
try (var client = new CopilotClient(options)) {
374+
// Calling ping (which calls ensureConnected) without start() should throw
375+
assertThrows(IllegalStateException.class, () -> client.ping("test"));
376+
}
377+
}
378+
379+
// ===== close() idempotency =====
380+
381+
@Test
382+
void testCloseIsIdempotent() {
383+
var client = new CopilotClient();
384+
385+
// First close
386+
client.close();
387+
// Second close should not throw
388+
assertDoesNotThrow(() -> client.close());
389+
}
390+
391+
@Test
392+
void testCloseAfterFailedStart() throws Exception {
393+
var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false);
394+
var client = new CopilotClient(options);
395+
396+
CompletableFuture<Void> startFuture = client.start();
397+
try {
398+
startFuture.get();
399+
} catch (ExecutionException e) {
400+
// Expected
401+
}
402+
403+
// close() after a failed start should not throw
404+
assertDoesNotThrow(() -> client.close());
405+
}
406+
407+
// ===== stop() with no connection =====
408+
409+
@Test
410+
void testStopWithNoConnectionCompletes() throws Exception {
411+
try (var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false))) {
412+
// stop() without start() should complete without error
413+
client.stop().get();
414+
assertEquals(ConnectionState.DISCONNECTED, client.getState());
415+
}
416+
}
417+
418+
@Test
419+
void testForceStopWithNoConnectionCompletes() throws Exception {
420+
try (var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false))) {
421+
// forceStop() without start() should complete without error
422+
client.forceStop().get();
423+
assertEquals(ConnectionState.DISCONNECTED, client.getState());
424+
}
425+
}
426+
427+
// ===== start() idempotency =====
428+
429+
@Test
430+
void testStartIsIdempotentSingleConnectionAttempt() throws Exception {
431+
var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false);
432+
433+
try (var client = new CopilotClient(options)) {
434+
client.start();
435+
client.start();
436+
437+
// Both calls should result in the same state (single connection attempt)
438+
ConnectionState state = client.getState();
439+
assertTrue(state == ConnectionState.CONNECTING || state == ConnectionState.ERROR,
440+
"State should be CONNECTING or ERROR after start(), was: " + state);
441+
}
442+
}
443+
444+
// ===== null options defaulting =====
445+
446+
@Test
447+
void testNullOptionsDefaultsToEmpty() {
448+
try (var client = new CopilotClient(null)) {
449+
assertEquals(ConnectionState.DISCONNECTED, client.getState());
450+
}
451+
}
326452
}

0 commit comments

Comments
 (0)