/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ using Xunit; namespace GitHub.Copilot.SDK.Test; // These tests bypass E2ETestBase because they are about how the CLI subprocess is started // Other test classes should instead inherit from E2ETestBase public class ClientTests { [Fact] public async Task Should_Start_And_Connect_To_Server_Using_Stdio() { using var client = new CopilotClient(new CopilotClientOptions { UseStdio = true }); try { await client.StartAsync(); Assert.Equal(ConnectionState.Connected, client.State); var pong = await client.PingAsync("test message"); Assert.Equal("pong: test message", pong.Message); Assert.True(pong.Timestamp >= 0); await client.StopAsync(); Assert.Equal(ConnectionState.Disconnected, client.State); } finally { await client.ForceStopAsync(); } } [Fact] public async Task Should_Start_And_Connect_To_Server_Using_Tcp() { using var client = new CopilotClient(new CopilotClientOptions { UseStdio = false }); try { await client.StartAsync(); Assert.Equal(ConnectionState.Connected, client.State); var pong = await client.PingAsync("test message"); Assert.Equal("pong: test message", pong.Message); await client.StopAsync(); } finally { await client.ForceStopAsync(); } } [Fact] public async Task Should_Force_Stop_Without_Cleanup() { using var client = new CopilotClient(new CopilotClientOptions()); await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); await client.ForceStopAsync(); Assert.Equal(ConnectionState.Disconnected, client.State); } [Fact] public async Task Should_Get_Status_With_Version_And_Protocol_Info() { using var client = new CopilotClient(new CopilotClientOptions { UseStdio = true }); try { await client.StartAsync(); var status = await client.GetStatusAsync(); Assert.NotNull(status.Version); Assert.NotEmpty(status.Version); Assert.True(status.ProtocolVersion >= 1); await client.StopAsync(); } finally { await client.ForceStopAsync(); } } [Fact] public async Task Should_Get_Auth_Status() { using var client = new CopilotClient(new CopilotClientOptions { UseStdio = true }); try { await client.StartAsync(); var authStatus = await client.GetAuthStatusAsync(); // isAuthenticated is a bool, just verify we got a response if (authStatus.IsAuthenticated) { Assert.NotNull(authStatus.AuthType); Assert.NotNull(authStatus.StatusMessage); } await client.StopAsync(); } finally { await client.ForceStopAsync(); } } [Fact] public async Task Should_List_Models_When_Authenticated() { using var client = new CopilotClient(new CopilotClientOptions { UseStdio = true }); try { await client.StartAsync(); var authStatus = await client.GetAuthStatusAsync(); if (!authStatus.IsAuthenticated) { // Skip if not authenticated - models.list requires auth await client.StopAsync(); return; } var models = await client.ListModelsAsync(); Assert.NotNull(models); if (models.Count > 0) { var model = models[0]; Assert.NotNull(model.Id); Assert.NotEmpty(model.Id); Assert.NotNull(model.Name); Assert.NotNull(model.Capabilities); } await client.StopAsync(); } finally { await client.ForceStopAsync(); } } [Fact] public void Should_Accept_GitHubToken_Option() { var options = new CopilotClientOptions { GitHubToken = "gho_test_token" }; Assert.Equal("gho_test_token", options.GitHubToken); } [Fact] public void Should_Default_UseLoggedInUser_To_Null() { var options = new CopilotClientOptions(); Assert.Null(options.UseLoggedInUser); } [Fact] public void Should_Allow_Explicit_UseLoggedInUser_False() { var options = new CopilotClientOptions { UseLoggedInUser = false }; Assert.False(options.UseLoggedInUser); } [Fact] public void Should_Allow_Explicit_UseLoggedInUser_True_With_GitHubToken() { var options = new CopilotClientOptions { GitHubToken = "gho_test_token", UseLoggedInUser = true }; Assert.True(options.UseLoggedInUser); } [Fact] public void Should_Throw_When_GitHubToken_Used_With_CliUrl() { Assert.Throws(() => { _ = new CopilotClient(new CopilotClientOptions { CliUrl = "localhost:8080", GitHubToken = "gho_test_token" }); }); } [Fact] public void Should_Throw_When_UseLoggedInUser_Used_With_CliUrl() { Assert.Throws(() => { _ = new CopilotClient(new CopilotClientOptions { CliUrl = "localhost:8080", UseLoggedInUser = false }); }); } [Fact] public async Task Should_Not_Throw_When_Disposing_Session_After_Stopping_Client() { await using var client = new CopilotClient(new CopilotClientOptions()); await using var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); await client.StopAsync(); } [Fact] public async Task Should_Report_Error_With_Stderr_When_CLI_Fails_To_Start() { var client = new CopilotClient(new CopilotClientOptions { CliArgs = new[] { "--nonexistent-flag-for-testing" }, UseStdio = true }); var ex = await Assert.ThrowsAsync(async () => { await client.StartAsync(); }); var errorMessage = ex.Message; // Verify we get the stderr output in the error message Assert.Contains("stderr", errorMessage, StringComparison.OrdinalIgnoreCase); Assert.Contains("nonexistent", errorMessage, StringComparison.OrdinalIgnoreCase); // Verify subsequent calls also fail (don't hang) var ex2 = await Assert.ThrowsAnyAsync(async () => { var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); await session.SendAsync(new MessageOptions { Prompt = "test" }); }); Assert.Contains("exited", ex2.Message, StringComparison.OrdinalIgnoreCase); // Cleanup - ForceStop should handle the disconnected state gracefully try { await client.ForceStopAsync(); } catch (Exception) { /* Expected */ } } [Fact] public async Task Should_Throw_When_CreateSession_Called_Without_PermissionHandler() { using var client = new CopilotClient(new CopilotClientOptions()); var ex = await Assert.ThrowsAsync(async () => { await client.CreateSessionAsync(new SessionConfig()); }); Assert.Contains("OnPermissionRequest", ex.Message); Assert.Contains("is required", ex.Message); } [Fact] public async Task Should_Throw_When_ResumeSession_Called_Without_PermissionHandler() { using var client = new CopilotClient(new CopilotClientOptions()); var ex = await Assert.ThrowsAsync(async () => { await client.ResumeSessionAsync("some-session-id", new ResumeSessionConfig()); }); Assert.Contains("OnPermissionRequest", ex.Message); Assert.Contains("is required", ex.Message); } }