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