/*---------------------------------------------------------------------------------------------
* 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);
}
}