From 4e3b7e17524fe03037ee536cfb3a8fa1703222cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 05:28:36 +0000 Subject: [PATCH 1/3] Initial plan From 162dfa536399996530e95661bee317de6cedaef9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 05:36:17 +0000 Subject: [PATCH 2/3] test: replace FindTestProjectPath-based tests with minimal on-disk fixture tests Agent-Logs-Url: https://github.com/jongalloway/dotnet-mcp/sessions/b72d0d93-5d1c-4fb4-8b20-7a2315524505 Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com> --- .../ProjectAnalysisHelperTests.cs | 295 ++++++++++++++---- 1 file changed, 232 insertions(+), 63 deletions(-) diff --git a/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs b/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs index d62487b..d4c7243 100644 --- a/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs +++ b/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs @@ -64,93 +64,262 @@ public async Task ValidateProjectAsync_WithNonExistentFile_ReturnsErrorJson() } [Fact] - public async Task AnalyzeProjectAsync_WithValidProject_ReturnsSuccessJson() + public async Task AnalyzeProjectAsync_WithMinimalProject_ReturnsSuccessJson() { - // Arrange - use the test project itself - var testProjectPath = FindTestProjectPath(); - if (testProjectPath == null) + var projectFile = CreateTempProject(""" + + + net8.0 + Exe + enable + + + """); + + try + { + var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(projectFile, _logger); + + Assert.NotNull(result); + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + Assert.Equal(projectFile, json.RootElement.GetProperty("projectPath").GetString()); + Assert.Equal("Test", json.RootElement.GetProperty("projectName").GetString()); + Assert.True(json.RootElement.TryGetProperty("targetFrameworks", out _)); + Assert.Equal("Exe", json.RootElement.GetProperty("outputType").GetString()); + Assert.True(json.RootElement.TryGetProperty("packageReferences", out _)); + Assert.Equal("enable", json.RootElement.GetProperty("nullable").GetString()); + } + finally { - // Skip test if we can't find the project file - return; + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } } + } - // Act - var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(testProjectPath, _logger); + [Fact] + public async Task AnalyzeProjectAsync_WithPackageReferences_ReturnsPackageInfo() + { + var projectFile = CreateTempProject(""" + + + net8.0 + + + + + + + """); - // Assert - Assert.NotNull(result); - var json = JsonDocument.Parse(result); - Assert.True(json.RootElement.GetProperty("success").GetBoolean()); - Assert.True(json.RootElement.TryGetProperty("projectPath", out _)); - Assert.True(json.RootElement.TryGetProperty("targetFrameworks", out _)); - Assert.True(json.RootElement.TryGetProperty("outputType", out _)); - Assert.True(json.RootElement.TryGetProperty("packageReferences", out _)); + try + { + var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(projectFile, _logger); + + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + + var packages = json.RootElement.GetProperty("packageReferences").EnumerateArray().ToList(); + Assert.Equal(2, packages.Count); + + var names = packages.Select(p => p.GetProperty("name").GetString()).ToHashSet(); + Assert.Contains("Newtonsoft.Json", names); + Assert.Contains("Serilog", names); + + var newtonsoftPkg = packages.First(p => p.GetProperty("name").GetString() == "Newtonsoft.Json"); + Assert.Equal("13.0.3", newtonsoftPkg.GetProperty("version").GetString()); + } + finally + { + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + } } [Fact] - public async Task AnalyzeDependenciesAsync_WithValidProject_ReturnsSuccessJson() + public async Task AnalyzeProjectAsync_WithMultipleTargetFrameworks_ReturnsAllFrameworks() { - // Arrange - use the test project itself - var testProjectPath = FindTestProjectPath(); - if (testProjectPath == null) + var projectFile = CreateTempProject(""" + + + net8.0;net9.0 + + + """); + + try { - // Skip test if we can't find the project file - return; + var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(projectFile, _logger); + + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + + var frameworks = json.RootElement.GetProperty("targetFrameworks") + .EnumerateArray() + .Select(f => f.GetString()) + .ToList(); + + Assert.Contains("net8.0", frameworks); + Assert.Contains("net9.0", frameworks); + } + finally + { + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } } + } - // Act - var result = await ProjectAnalysisHelper.AnalyzeDependenciesAsync(testProjectPath, _logger); + [Fact] + public async Task AnalyzeDependenciesAsync_WithMinimalProject_ReturnsSuccessJson() + { + var projectFile = CreateTempProject(""" + + + net8.0 + + + """); - // Assert - Assert.NotNull(result); - var json = JsonDocument.Parse(result); - Assert.True(json.RootElement.GetProperty("success").GetBoolean()); - Assert.True(json.RootElement.TryGetProperty("directPackageDependencies", out _)); - Assert.True(json.RootElement.TryGetProperty("directProjectDependencies", out _)); - Assert.True(json.RootElement.TryGetProperty("totalDirectDependencies", out _)); + try + { + var result = await ProjectAnalysisHelper.AnalyzeDependenciesAsync(projectFile, _logger); + + Assert.NotNull(result); + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + Assert.True(json.RootElement.TryGetProperty("directPackageDependencies", out _)); + Assert.True(json.RootElement.TryGetProperty("directProjectDependencies", out _)); + Assert.Equal(0, json.RootElement.GetProperty("totalDirectDependencies").GetInt32()); + } + finally + { + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + } + } + + [Fact] + public async Task AnalyzeDependenciesAsync_WithPackageReferences_ReturnsDependencies() + { + var projectFile = CreateTempProject(""" + + + net8.0 + + + + + + """); + + try + { + var result = await ProjectAnalysisHelper.AnalyzeDependenciesAsync(projectFile, _logger); + + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + + var packages = json.RootElement.GetProperty("directPackageDependencies").EnumerateArray().ToList(); + Assert.Single(packages); + Assert.Equal("Newtonsoft.Json", packages[0].GetProperty("name").GetString()); + Assert.Equal("13.0.3", packages[0].GetProperty("version").GetString()); + Assert.Equal(1, json.RootElement.GetProperty("totalDirectDependencies").GetInt32()); + } + finally + { + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + } } [Fact] - public async Task ValidateProjectAsync_WithValidProject_ReturnsSuccessJson() + public async Task ValidateProjectAsync_WithHealthyProject_ReturnsSuccessAndIsValid() { - // Arrange - use the test project itself - var testProjectPath = FindTestProjectPath(); - if (testProjectPath == null) + var projectFile = CreateTempProject(""" + + + net8.0 + Exe + enable + + + """); + + try { - // Skip test if we can't find the project file - return; + var result = await ProjectAnalysisHelper.ValidateProjectAsync(projectFile, _logger); + + Assert.NotNull(result); + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + Assert.True(json.RootElement.GetProperty("isValid").GetBoolean()); + Assert.True(json.RootElement.TryGetProperty("errors", out _)); + Assert.True(json.RootElement.TryGetProperty("warnings", out _)); + Assert.True(json.RootElement.TryGetProperty("recommendations", out _)); + Assert.Equal(0, json.RootElement.GetProperty("errors").GetArrayLength()); + } + finally + { + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } } + } - // Act - var result = await ProjectAnalysisHelper.ValidateProjectAsync(testProjectPath, _logger); + [Fact] + public async Task ValidateProjectAsync_WithMissingTargetFramework_ReturnsErrors() + { + var projectFile = CreateTempProject(""" + + + + + """); - // Assert - Assert.NotNull(result); - var json = JsonDocument.Parse(result); - Assert.True(json.RootElement.GetProperty("success").GetBoolean()); - Assert.True(json.RootElement.GetProperty("isValid").GetBoolean()); - Assert.True(json.RootElement.TryGetProperty("errors", out _)); - Assert.True(json.RootElement.TryGetProperty("warnings", out _)); - Assert.True(json.RootElement.TryGetProperty("recommendations", out _)); + try + { + var result = await ProjectAnalysisHelper.ValidateProjectAsync(projectFile, _logger); + + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + Assert.False(json.RootElement.GetProperty("isValid").GetBoolean()); + + var errors = json.RootElement.GetProperty("errors") + .EnumerateArray() + .Select(e => e.GetString()) + .ToList(); + + Assert.Contains(errors, e => e != null && e.Contains("No target framework")); + } + finally + { + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + } } - private string? FindTestProjectPath() + [Fact] + public async Task ValidateProjectAsync_WithNullableEnabled_DoesNotRecommendNullable() { - // Try to find the test project file - var currentDir = Directory.GetCurrentDirectory(); - - // Common patterns where the test project might be - var possiblePaths = new[] - { - Path.Join(currentDir, "DotNetMcp.Tests.csproj"), - Path.Join(currentDir, "..", "DotNetMcp.Tests", "DotNetMcp.Tests.csproj"), - Path.Join(currentDir, "..", "..", "DotNetMcp.Tests", "DotNetMcp.Tests.csproj"), - }; - - return possiblePaths - .Where(File.Exists) - .Select(Path.GetFullPath) - .FirstOrDefault(); + var projectFile = CreateTempProject(""" + + + net8.0 + enable + + + """); + + try + { + var result = await ProjectAnalysisHelper.ValidateProjectAsync(projectFile, _logger); + + var json = JsonDocument.Parse(result); + Assert.True(json.RootElement.GetProperty("success").GetBoolean()); + + var recommendations = json.RootElement.GetProperty("recommendations") + .EnumerateArray() + .Select(r => r.GetString()) + .ToList(); + + Assert.DoesNotContain(recommendations, r => r != null && r.Contains("nullable reference types")); + } + finally + { + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + } } private static string CreateTempProject(string content) From 8eb1cff7a0b01d4acefd9c43bf03a23c305c52ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 18:10:19 +0000 Subject: [PATCH 3/3] fix: use `using var` for JsonDocument and narrow catch clauses in new fixture tests Agent-Logs-Url: https://github.com/jongalloway/dotnet-mcp/sessions/28012c91-2845-4d28-a144-5134ee6c6977 Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com> --- .../ProjectAnalysisHelperTests.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs b/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs index d4c7243..c4d222d 100644 --- a/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs +++ b/DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs @@ -81,7 +81,7 @@ public async Task AnalyzeProjectAsync_WithMinimalProject_ReturnsSuccessJson() var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(projectFile, _logger); Assert.NotNull(result); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); Assert.Equal(projectFile, json.RootElement.GetProperty("projectPath").GetString()); Assert.Equal("Test", json.RootElement.GetProperty("projectName").GetString()); @@ -92,7 +92,7 @@ public async Task AnalyzeProjectAsync_WithMinimalProject_ReturnsSuccessJson() } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } } @@ -115,7 +115,7 @@ public async Task AnalyzeProjectAsync_WithPackageReferences_ReturnsPackageInfo() { var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(projectFile, _logger); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); var packages = json.RootElement.GetProperty("packageReferences").EnumerateArray().ToList(); @@ -130,7 +130,7 @@ public async Task AnalyzeProjectAsync_WithPackageReferences_ReturnsPackageInfo() } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } } @@ -149,7 +149,7 @@ public async Task AnalyzeProjectAsync_WithMultipleTargetFrameworks_ReturnsAllFra { var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(projectFile, _logger); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); var frameworks = json.RootElement.GetProperty("targetFrameworks") @@ -162,7 +162,7 @@ public async Task AnalyzeProjectAsync_WithMultipleTargetFrameworks_ReturnsAllFra } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } } @@ -182,7 +182,7 @@ public async Task AnalyzeDependenciesAsync_WithMinimalProject_ReturnsSuccessJson var result = await ProjectAnalysisHelper.AnalyzeDependenciesAsync(projectFile, _logger); Assert.NotNull(result); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); Assert.True(json.RootElement.TryGetProperty("directPackageDependencies", out _)); Assert.True(json.RootElement.TryGetProperty("directProjectDependencies", out _)); @@ -190,7 +190,7 @@ public async Task AnalyzeDependenciesAsync_WithMinimalProject_ReturnsSuccessJson } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } } @@ -212,7 +212,7 @@ public async Task AnalyzeDependenciesAsync_WithPackageReferences_ReturnsDependen { var result = await ProjectAnalysisHelper.AnalyzeDependenciesAsync(projectFile, _logger); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); var packages = json.RootElement.GetProperty("directPackageDependencies").EnumerateArray().ToList(); @@ -223,7 +223,7 @@ public async Task AnalyzeDependenciesAsync_WithPackageReferences_ReturnsDependen } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } } @@ -245,7 +245,7 @@ public async Task ValidateProjectAsync_WithHealthyProject_ReturnsSuccessAndIsVal var result = await ProjectAnalysisHelper.ValidateProjectAsync(projectFile, _logger); Assert.NotNull(result); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); Assert.True(json.RootElement.GetProperty("isValid").GetBoolean()); Assert.True(json.RootElement.TryGetProperty("errors", out _)); @@ -255,7 +255,7 @@ public async Task ValidateProjectAsync_WithHealthyProject_ReturnsSuccessAndIsVal } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } } @@ -273,7 +273,7 @@ public async Task ValidateProjectAsync_WithMissingTargetFramework_ReturnsErrors( { var result = await ProjectAnalysisHelper.ValidateProjectAsync(projectFile, _logger); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); Assert.False(json.RootElement.GetProperty("isValid").GetBoolean()); @@ -286,7 +286,7 @@ public async Task ValidateProjectAsync_WithMissingTargetFramework_ReturnsErrors( } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } } @@ -306,7 +306,7 @@ public async Task ValidateProjectAsync_WithNullableEnabled_DoesNotRecommendNulla { var result = await ProjectAnalysisHelper.ValidateProjectAsync(projectFile, _logger); - var json = JsonDocument.Parse(result); + using var json = JsonDocument.Parse(result); Assert.True(json.RootElement.GetProperty("success").GetBoolean()); var recommendations = json.RootElement.GetProperty("recommendations") @@ -318,7 +318,7 @@ public async Task ValidateProjectAsync_WithNullableEnabled_DoesNotRecommendNulla } finally { - try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch { /* best-effort */ } + try { Directory.Delete(Path.GetDirectoryName(projectFile)!, recursive: true); } catch (IOException) { /* best-effort */ } catch (UnauthorizedAccessException) { /* best-effort */ } } }