/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; namespace GitHub.Copilot.Test.E2E; [Trait(E2ETestTraits.Backend, E2ETestTraits.CapiOnly)] public class PerSessionAuthE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "per-session-auth", output) { /// /// Creates a client with COPILOT_DEBUG_GITHUB_API_URL redirected to the proxy /// so per-session auth token resolution (fetchCopilotUser) is intercepted. /// private CopilotClient CreateAuthTestClient() { var env = new Dictionary(Ctx.GetEnvironment()) { ["COPILOT_DEBUG_GITHUB_API_URL"] = Ctx.ProxyUrl, }; // Disable the harness's auto-injected client token so the per-session // auth tests validate only session-scoped tokens. return Ctx.CreateClient(environment: env, autoInjectGitHubToken: false); } private CopilotClient CreateNoAuthTestClient() { var env = WithoutAuthEnv(Ctx.GetEnvironment()); env["COPILOT_DEBUG_GITHUB_API_URL"] = Ctx.ProxyUrl; return Ctx.CreateClient(options: new CopilotClientOptions { UseLoggedInUser = false, }, autoInjectGitHubToken: false, environment: env); } private static Dictionary WithoutAuthEnv(Dictionary env) { var result = new Dictionary(env) { ["COPILOT_SDK_AUTH_TOKEN"] = "", ["GH_TOKEN"] = "", ["GITHUB_TOKEN"] = "", }; return result; } private async Task SetupCopilotUsersAsync() { await Ctx.SetCopilotUserByTokenAsync("token-alice", new CopilotUserConfig( Login: "alice", CopilotPlan: "individual_pro", Endpoints: new CopilotUserEndpoints(Api: Ctx.ProxyUrl, Telemetry: "https://localhost:1/telemetry"), AnalyticsTrackingId: "alice-tracking-id" )); await Ctx.SetCopilotUserByTokenAsync("token-bob", new CopilotUserConfig( Login: "bob", CopilotPlan: "business", Endpoints: new CopilotUserEndpoints(Api: Ctx.ProxyUrl, Telemetry: "https://localhost:1/telemetry"), AnalyticsTrackingId: "bob-tracking-id" )); } private CopilotClient? _authClient; private CopilotClient AuthClient => _authClient ??= CreateAuthTestClient(); [Fact] public async Task ShouldAuthenticateWithGitHubToken() { await SetupCopilotUsersAsync(); await using var session = await Ctx.CreateSessionAsync(AuthClient, new SessionConfig { GitHubToken = "token-alice", OnPermissionRequest = PermissionHandler.ApproveAll, }); var status = await session.Rpc.GitHubAuth.GetStatusAsync(); Assert.True(status.IsAuthenticated); Assert.Equal("alice", status.Login); } [Fact] public async Task ShouldIsolateAuthBetweenSessions() { await SetupCopilotUsersAsync(); await using var sessionA = await Ctx.CreateSessionAsync(AuthClient, new SessionConfig { GitHubToken = "token-alice", OnPermissionRequest = PermissionHandler.ApproveAll, }); await using var sessionB = await Ctx.CreateSessionAsync(AuthClient, new SessionConfig { GitHubToken = "token-bob", OnPermissionRequest = PermissionHandler.ApproveAll, }); var statusA = await sessionA.Rpc.GitHubAuth.GetStatusAsync(); Assert.True(statusA.IsAuthenticated); Assert.Equal("alice", statusA.Login); var statusB = await sessionB.Rpc.GitHubAuth.GetStatusAsync(); Assert.True(statusB.IsAuthenticated); Assert.Equal("bob", statusB.Login); } [Fact] public async Task ShouldBeUnauthenticatedWithoutToken() { var noAuthClient = CreateNoAuthTestClient(); await using var session = await Ctx.CreateSessionAsync(noAuthClient, new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, }); var status = await session.Rpc.GitHubAuth.GetStatusAsync(); // Without a per-session GitHub token, there is no per-session identity. Assert.True(string.IsNullOrEmpty(status.Login), $"Expected no per-session login without token, got {status.Login}"); } [Fact] public async Task ShouldFailWithInvalidToken() { await SetupCopilotUsersAsync(); var ex = await Assert.ThrowsAnyAsync(() => Ctx.CreateSessionAsync(AuthClient, new SessionConfig { GitHubToken = "invalid-token", OnPermissionRequest = PermissionHandler.ApproveAll, })); Assert.Contains("401 Unauthorized", ex.ToString(), StringComparison.OrdinalIgnoreCase); } }