Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 21 additions & 24 deletions dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ private Dictionary<string, string> ExtensionsEnabledEnvironment()
return env;
}

/// <summary>
/// Creates a client with the EXTENSIONS feature flag and --yolo CLI arg.
/// --yolo auto-approves extension permission gates at the CLI level,
/// preventing tests from breaking when new permission gates are added
/// (e.g., extension-permission-access from copilot-agent-runtime#6024).
/// </summary>
private CopilotClient CreateExtensionsClient()
{
return Ctx.CreateClient(options: new CopilotClientOptions
{
CliArgs = ["--yolo"],
Environment = ExtensionsEnabledEnvironment(),
});
}

/// <summary>
/// Writes a minimal user extension into <c>{HomeDir}/extensions/{name}/extension.mjs</c>.
/// The body imports <c>@github/copilot-sdk/extension</c>, calls <c>joinSession</c>
Expand Down Expand Up @@ -172,10 +187,7 @@ public async Task Discovers_Loads_And_Reports_Running_Extension(ExtensionSource
throw new ArgumentOutOfRangeException(nameof(source), source, null);
}

await using var client = Ctx.CreateClient(options: new CopilotClientOptions
{
Environment = ExtensionsEnabledEnvironment(),
});
await using var client = CreateExtensionsClient();

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Expand All @@ -200,10 +212,7 @@ public async Task Disable_Then_Enable_Cycles_Extension_Status()
var extName = CreateUserExtension();
var extId = $"user:{extName}";

await using var client = Ctx.CreateClient(options: new CopilotClientOptions
{
Environment = ExtensionsEnabledEnvironment(),
});
await using var client = CreateExtensionsClient();

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Expand All @@ -229,10 +238,7 @@ public async Task Disable_Then_Enable_Cycles_Extension_Status()
public async Task Reload_Picks_Up_Extension_Added_After_Session_Create()
{
// Start the session BEFORE writing the extension so the initial discovery sees nothing.
await using var client = Ctx.CreateClient(options: new CopilotClientOptions
{
Environment = ExtensionsEnabledEnvironment(),
});
await using var client = CreateExtensionsClient();

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Expand Down Expand Up @@ -277,10 +283,7 @@ public async Task Failed_Extension_Reports_Failed_Status()

var extId = $"user:{extName}";

await using var client = Ctx.CreateClient(options: new CopilotClientOptions
{
Environment = ExtensionsEnabledEnvironment(),
});
await using var client = CreateExtensionsClient();

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Expand All @@ -301,10 +304,7 @@ public async Task Multiple_Extensions_Are_Discovered_Independently()
var ext1Id = $"user:{ext1Name}";
var ext2Id = $"user:{ext2Name}";

await using var client = Ctx.CreateClient(options: new CopilotClientOptions
{
Environment = ExtensionsEnabledEnvironment(),
});
await using var client = CreateExtensionsClient();

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Expand All @@ -326,10 +326,7 @@ public async Task Reload_Preserves_Disabled_State_Across_Calls()
var extName = CreateUserExtension(prefix: "persistent-disable");
var extId = $"user:{extName}";

await using var client = Ctx.CreateClient(options: new CopilotClientOptions
{
Environment = ExtensionsEnabledEnvironment(),
});
await using var client = CreateExtensionsClient();

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Expand Down
22 changes: 20 additions & 2 deletions dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,16 @@ public async Task Should_List_Plugins()
[Fact]
public async Task Should_List_Extensions()
{
var session = await CreateSessionAsync();
// Use --yolo to auto-approve extension permission gates at the CLI level,
// preventing breakage from new gates (e.g., extension-permission-access).
await using var yoloClient = Ctx.CreateClient(options: new CopilotClientOptions
{
CliArgs = ["--yolo"],
});
await using var session = await yoloClient.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
});

var result = await session.Rpc.Extensions.ListAsync();

Expand Down Expand Up @@ -175,7 +184,16 @@ await AssertFailureAsync(
[Fact]
public async Task Should_Report_Error_When_Extensions_Are_Not_Available()
{
var session = await CreateSessionAsync();
// Use --yolo to auto-approve extension permission gates at the CLI level,
// preventing breakage from new gates (e.g., extension-permission-access).
await using var yoloClient = Ctx.CreateClient(options: new CopilotClientOptions
{
CliArgs = ["--yolo"],
});
await using var session = await yoloClient.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
});

await AssertFailureAsync(
() => session.Rpc.Extensions.EnableAsync("missing-extension"),
Expand Down
6 changes: 5 additions & 1 deletion go/internal/e2e/rpc_mcp_and_skills_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import (
// Tests session-scoped MCP, skills, plugins, and extensions RPCs.
func TestRpcMcpAndSkillsE2E(t *testing.T) {
ctx := testharness.NewTestContext(t)
client := ctx.NewClient()
// --yolo auto-approves extension permission gates at the CLI level,
// preventing breakage from new gates (e.g., extension-permission-access).
client := ctx.NewClient(func(o *copilot.ClientOptions) {
o.CLIArgs = []string{"--yolo"}
})
t.Cleanup(func() { client.ForceStop() })

t.Run("should list and toggle session skills", func(t *testing.T) {
Expand Down
6 changes: 5 additions & 1 deletion nodejs/test/e2e/rpc_mcp_and_skills.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import type { MCPServerConfig } from "../../src/index.js";
import { createSdkTestContext } from "./harness/sdkTestContext.js";

describe("Session MCP and skills RPC", async () => {
const { copilotClient: client, workDir } = await createSdkTestContext();
// --yolo auto-approves extension permission gates at the CLI level,
// preventing breakage from new gates (e.g., extension-permission-access).
const { copilotClient: client, workDir } = await createSdkTestContext({
copilotClientOptions: { cliArgs: ["--yolo"] },
});

function createSkill(skillsDir: string, skillName: string, description: string): void {
const skillSubdir = path.join(skillsDir, skillName);
Expand Down
13 changes: 13 additions & 0 deletions python/e2e/test_rpc_mcp_and_skills_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pathlib import Path

import pytest
import pytest_asyncio

from copilot.generated.rpc import (
ExtensionsDisableRequest,
Expand All @@ -28,6 +29,18 @@
pytestmark = pytest.mark.asyncio(loop_scope="module")


# --yolo auto-approves extension permission gates at the CLI level,
# preventing breakage from new gates (e.g., extension-permission-access).
@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def ctx(request):
"""Module-scoped context with --yolo for extension test hardening."""
context = E2ETestContext()
await context.setup(cli_args=["--yolo"])
yield context
any_failed = request.session.stash.get("any_test_failed", False)
await context.teardown(test_failed=any_failed)


def _create_skill(skills_dir: Path, skill_name: str, description: str) -> None:
skill_subdir = skills_dir / skill_name
skill_subdir.mkdir(parents=True, exist_ok=True)
Expand Down
9 changes: 7 additions & 2 deletions python/e2e/testharness/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@ def __init__(self):
self._proxy: CapiProxy | None = None
self._client: CopilotClient | None = None

async def setup(self):
"""Set up the test context with a shared client."""
async def setup(self, cli_args: list[str] | None = None):
"""Set up the test context with a shared client.

Args:
cli_args: Optional extra CLI arguments passed to the CLI process.
"""
self.cli_path = get_cli_path_for_tests()

self.home_dir = os.path.realpath(tempfile.mkdtemp(prefix="copilot-test-config-"))
Expand All @@ -69,6 +73,7 @@ async def setup(self):
self._client = CopilotClient(
SubprocessConfig(
cli_path=self.cli_path,
cli_args=cli_args or [],
cwd=self.work_dir,
env=self.get_env(),
github_token=github_token,
Expand Down
Loading