| File: Agents\VsCodeAgentEnvironmentScannerTests.cs | Web Access |
| Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Cli.Agents; using Aspire.Cli.Agents.VsCode; using Aspire.Cli.Tests.Utils; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging.Abstractions; using Semver; namespace Aspire.Cli.Tests.Agents; public class VsCodeAgentEnvironmentScannerTests(ITestOutputHelper outputHelper) { [Theory] [InlineData(".")] [InlineData("subdir")] public async Task ScanAsync_WithProjectDirectory_DetectsVsCodeWithoutCallingRunner(string relativeWorkingDirectory) { using var workspace = TemporaryWorkspace.CreateForCli(outputHelper); workspace.CreateDirectory(".vscode"); var workingDirectory = relativeWorkingDirectory == "." ? workspace.WorkspaceRoot : workspace.CreateDirectory(relativeWorkingDirectory); var runner = new FakeVsCodeCliRunner(null); var scanner = new VsCodeAgentEnvironmentScanner(runner, CreateExecutionContext(workingDirectory), new TestEnvironment(), NullLogger<VsCodeAgentEnvironmentScanner>.Instance); var context = new AgentEnvironmentScanContext { WorkingDirectory = workingDirectory, RepositoryRoot = workspace.WorkspaceRoot }; var before = Directory.GetFileSystemEntries(workspace.WorkspaceRoot.FullName, "*", SearchOption.AllDirectories); var environments = await scanner.ScanAsync(context, CancellationToken.None).DefaultTimeout(); Assert.Equal(AgentEnvironment.VsCode, environments); Assert.False(runner.WasCalled); Assert.Equal(before, Directory.GetFileSystemEntries(workspace.WorkspaceRoot.FullName, "*", SearchOption.AllDirectories)); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ScanAsync_IgnoresHomeAndDirectoriesAboveRepositoryRoot(bool directoryIsHome) { using var workspace = TemporaryWorkspace.CreateForCli(outputHelper); workspace.CreateDirectory(".vscode"); var childDirectory = workspace.CreateDirectory("subdir"); var homeDirectory = directoryIsHome ? workspace.WorkspaceRoot : workspace.CreateDirectory("fake-home"); var runner = new FakeVsCodeCliRunner(null); var scanner = new VsCodeAgentEnvironmentScanner(runner, TestExecutionContextHelper.CreateExecutionContext(childDirectory, homeDirectory: homeDirectory), new TestEnvironment(), NullLogger<VsCodeAgentEnvironmentScanner>.Instance); var context = new AgentEnvironmentScanContext { WorkingDirectory = childDirectory, RepositoryRoot = directoryIsHome ? workspace.WorkspaceRoot : childDirectory }; var environments = await scanner.ScanAsync(context, CancellationToken.None).DefaultTimeout(); Assert.Equal(AgentEnvironment.None, environments); Assert.True(runner.WasCalled); } [Theory] [InlineData("{ invalid json content")] [InlineData("")] [InlineData("""{"servers":{"aspire":{"command":"custom-aspire"}}}""")] public async Task ScanAsync_WithMcpConfiguration_DetectsVsCodeWithoutParsingOrModifyingFile(string content) { using var workspace = TemporaryWorkspace.CreateForCli(outputHelper); var configDirectory = workspace.CreateDirectory(".vscode"); var configPath = Path.Combine(configDirectory.FullName, "mcp.json"); await File.WriteAllTextAsync(configPath, content); var scanner = new VsCodeAgentEnvironmentScanner(new FakeVsCodeCliRunner(null), CreateExecutionContext(workspace.WorkspaceRoot), new TestEnvironment(), NullLogger<VsCodeAgentEnvironmentScanner>.Instance); var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot, RepositoryRoot = workspace.WorkspaceRoot }; var environments = await scanner.ScanAsync(context, CancellationToken.None).DefaultTimeout(); Assert.Equal(AgentEnvironment.VsCode, environments); Assert.Equal(content, await File.ReadAllTextAsync(configPath)); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ScanAsync_WithoutProjectDirectory_ProbesCliWithoutCreatingConfiguration(bool cliInstalled) { using var workspace = TemporaryWorkspace.CreateForCli(outputHelper); var runner = new FakeVsCodeCliRunner(cliInstalled ? new SemVersion(1, 85, 0) : null); var scanner = new VsCodeAgentEnvironmentScanner(runner, CreateExecutionContext(workspace.WorkspaceRoot), new TestEnvironment(), NullLogger<VsCodeAgentEnvironmentScanner>.Instance); var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot, RepositoryRoot = workspace.WorkspaceRoot }; var before = Directory.GetFileSystemEntries(workspace.WorkspaceRoot.FullName, "*", SearchOption.AllDirectories); var environments = await scanner.ScanAsync(context, CancellationToken.None).DefaultTimeout(); Assert.Equal(cliInstalled ? AgentEnvironment.VsCode : AgentEnvironment.None, environments); Assert.True(runner.WasCalled); Assert.Equal(before, Directory.GetFileSystemEntries(workspace.WorkspaceRoot.FullName, "*", SearchOption.AllDirectories)); } [Fact] public async Task ScanAsync_WhenInVsCode_DetectsEditorWithoutCallingRunner() { using var workspace = TemporaryWorkspace.CreateForCli(outputHelper); var runner = new FakeVsCodeCliRunner(null); var environment = new TestEnvironment(new Dictionary<string, string?> { ["TERM_PROGRAM"] = "vscode" }); var scanner = new VsCodeAgentEnvironmentScanner(runner, CreateExecutionContext(workspace.WorkspaceRoot), environment, NullLogger<VsCodeAgentEnvironmentScanner>.Instance); var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot, RepositoryRoot = workspace.WorkspaceRoot }; var environments = await scanner.ScanAsync(context, CancellationToken.None).DefaultTimeout(); Assert.Equal(AgentEnvironment.VsCode, environments); Assert.False(runner.WasCalled); } private static CliExecutionContext CreateExecutionContext(DirectoryInfo workingDirectory) { return TestExecutionContextHelper.CreateExecutionContext(workingDirectory, homeDirectory: new DirectoryInfo(Path.Combine(workingDirectory.FullName, "fake-home"))); } private sealed class FakeVsCodeCliRunner(SemVersion? version) : IVsCodeCliRunner { public bool WasCalled { get; private set; } public Task<SemVersion?> GetVersionAsync(VsCodeRunOptions options, CancellationToken cancellationToken) { WasCalled = true; return Task.FromResult(version); } } }