Skip to content

Commit 7b8b7e0

Browse files
stephentoubCopilot
andcommitted
Register default Copilot user in Java e2e harness to fix MCP tests
CLI 1.0.64-1 gates MCP enablement on the /copilot_internal/user response. Commit 9d696d2 routed that call directly to the replay proxy via COPILOT_DEBUG_GITHUB_API_URL, but the Java harness never registered a default user for the CLI's default token, so the proxy returned 401 'Bad credentials'. With no user, the third-party MCP policy resolver could not early-return allow-all and the MCP servers never reached CONNECTED, timing out McpAndAgentsTest after 60s. Register a default 'individual_pro' user at E2ETestContext.create(), matching the Go, Node, Python, and .NET harnesses. The proxy adds is_mcp_enabled:true (the global gate) and the snake_case copilot_plan makes the resolver early-return allow-all for non-org plans, avoiding a /copilot/mcp_registry call the proxy does not serve. Add a raw-map setCopilotUserByToken overload to CapiProxy so the registered user uses the same snake_case field shape the CLI reads; the existing 6-arg camelCase overload (used by per-session auth tests) is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d487453 commit 7b8b7e0

2 files changed

Lines changed: 74 additions & 4 deletions

File tree

java/src/test/java/com/github/copilot/CapiProxy.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,53 @@ public void setCopilotUserByToken(String token, String login, String copilotPlan
290290
}
291291
}
292292

293+
/**
294+
* Registers a raw Copilot user response for a given token on the
295+
* {@code /copilot_internal/user} endpoint.
296+
*
297+
* <p>
298+
* Unlike
299+
* {@link #setCopilotUserByToken(String, String, String, String, String, String)},
300+
* this posts the response object verbatim, so callers control the exact field
301+
* names the proxy returns to the CLI. This matters because the CLI reads
302+
* snake_case fields (e.g. {@code copilot_plan}, {@code is_mcp_enabled}) from
303+
* the raw user JSON to gate MCP enablement. Use this to register the default
304+
* e2e user with the same snake_case shape the Go, Node, Python, and .NET
305+
* harnesses post, keeping MCP behavior hermetic and consistent across SDKs.
306+
* </p>
307+
*
308+
* @param token
309+
* the GitHub token to configure
310+
* @param response
311+
* the raw user response object to return for the token (field names
312+
* are sent verbatim)
313+
* @throws IOException
314+
* if the request fails
315+
* @throws InterruptedException
316+
* if the request is interrupted
317+
*/
318+
public void setCopilotUserByToken(String token, Map<String, Object> response)
319+
throws IOException, InterruptedException {
320+
if (proxyUrl == null) {
321+
throw new IllegalStateException("Proxy not started");
322+
}
323+
324+
Map<String, Object> payload = new java.util.HashMap<>();
325+
payload.put("token", token);
326+
payload.put("response", response);
327+
328+
String body = MAPPER.writeValueAsString(payload);
329+
330+
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/copilot-user-config"))
331+
.header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build();
332+
333+
HttpResponse<String> response2 = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
334+
if (response2.statusCode() != 200) {
335+
throw new IOException(
336+
"Failed to set copilot user config: " + response2.statusCode() + ": " + response2.body());
337+
}
338+
}
339+
293340
/**
294341
* Stops the proxy server gracefully.
295342
*

java/src/test/java/com/github/copilot/E2ETestContext.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@
5555
public class E2ETestContext implements AutoCloseable {
5656

5757
private static final Logger LOG = Logger.getLogger(E2ETestContext.class.getName());
58+
59+
/**
60+
* The default GitHub token used by the CLI in e2e tests. The proxy resolves
61+
* this token to the default Copilot user registered at context creation.
62+
*/
63+
private static final String DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests";
5864
private static final Pattern SNAKE_CASE = Pattern.compile("[^a-zA-Z0-9]");
5965
private static final Pattern USER_CONTENT_PATTERN = Pattern
6066
.compile("^\\s+-\\s+role:\\s+user\\s*$\\s+content:\\s*(.+?)$", Pattern.MULTILINE);
@@ -97,6 +103,23 @@ public static E2ETestContext create() throws IOException, InterruptedException {
97103
CapiProxy proxy = new CapiProxy();
98104
String proxyUrl = proxy.start();
99105

106+
// Register a default Copilot user for the CLI's default token so the proxy's
107+
// /copilot_internal/user endpoint returns a valid user (HTTP 200) instead of
108+
// 401 "Bad credentials". CLI 1.0.64-1 gates MCP enablement on this user:
109+
// `is_mcp_enabled` (added by the proxy) is the global gate, and snake_case
110+
// `copilot_plan` makes the third-party MCP policy resolver early-return
111+
// allow-all for non-org plans (anything other than business/enterprise),
112+
// avoiding a /copilot/mcp_registry network call the proxy does not serve.
113+
// Without this, MCP servers never reach CONNECTED. This mirrors the Go,
114+
// Node, Python, and .NET harnesses, which all register the same default
115+
// individual_pro user at context creation.
116+
Map<String, Object> defaultUser = new HashMap<>();
117+
defaultUser.put("login", "e2e-test-user");
118+
defaultUser.put("copilot_plan", "individual_pro");
119+
defaultUser.put("endpoints", Map.of("api", proxyUrl, "telemetry", "https://localhost:1/telemetry"));
120+
defaultUser.put("analytics_tracking_id", "e2e-test-tracking-id");
121+
proxy.setCopilotUserByToken(DEFAULT_GITHUB_TOKEN, defaultUser);
122+
100123
return new E2ETestContext(cliPath, homeDir, workDir, proxyUrl, proxy, repoRoot);
101124
}
102125

@@ -282,8 +305,8 @@ public Map<String, String> getEnvironment() {
282305
env.put("REQUESTS_CA_BUNDLE", caFile);
283306
env.put("CURL_CA_BUNDLE", caFile);
284307
env.put("GIT_SSL_CAINFO", caFile);
285-
env.put("GH_TOKEN", "fake-token-for-e2e-tests");
286-
env.put("GITHUB_TOKEN", "fake-token-for-e2e-tests");
308+
env.put("GH_TOKEN", DEFAULT_GITHUB_TOKEN);
309+
env.put("GITHUB_TOKEN", DEFAULT_GITHUB_TOKEN);
287310
env.put("GH_ENTERPRISE_TOKEN", "");
288311
env.put("GITHUB_ENTERPRISE_TOKEN", "");
289312
}
@@ -298,7 +321,7 @@ public Map<String, String> getEnvironment() {
298321
*/
299322
public CopilotClient createClient() {
300323
CopilotClientOptions options = new CopilotClientOptions().setCliPath(cliPath).setCwd(workDir.toString())
301-
.setEnvironment(getEnvironment()).setGitHubToken("fake-token-for-e2e-tests");
324+
.setEnvironment(getEnvironment()).setGitHubToken(DEFAULT_GITHUB_TOKEN);
302325

303326
return new CopilotClient(options);
304327
}
@@ -323,7 +346,7 @@ public CopilotClient createClient(CopilotClientOptions options) {
323346
options.setEnvironment(getEnvironment());
324347
}
325348
if (options.getGitHubToken() == null) {
326-
options.setGitHubToken("fake-token-for-e2e-tests");
349+
options.setGitHubToken(DEFAULT_GITHUB_TOKEN);
327350
}
328351

329352
return new CopilotClient(options);

0 commit comments

Comments
 (0)