Skip to content
Merged
Changes from 3 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
295 changes: 232 additions & 63 deletions DotNetMcp.Tests/SdkIntegration/ProjectAnalysisHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,93 +64,262 @@
}

[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("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
""");

try
{
var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(projectFile, _logger);

Assert.NotNull(result);
var json = JsonDocument.Parse(result);
Assert.True(json.RootElement.GetProperty("success").GetBoolean());
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonDocument is IDisposable; not disposing it can retain pooled buffers and add memory pressure across the test suite. Prefer using var json = JsonDocument.Parse(result); in these new tests.

This issue also appears in the following locations of the same file:

  • line 118
  • line 152
  • line 185
  • line 215
  • line 248
  • ...and 2 more

Copilot uses AI. Check for mistakes.
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 */ }

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup swallows all exceptions (catch {}), which can hide unexpected failures (including non-I/O exceptions) and make test runs leave temp directories behind. Align with the rest of this file by only catching expected cleanup exceptions (e.g., IOException/UnauthorizedAccessException), or centralize cleanup in a helper that does that.

This issue also appears in the following locations of the same file:

  • line 133
  • line 165
  • line 193
  • line 226
  • line 258
  • ...and 2 more

Copilot uses AI. Check for mistakes.
}
}

// Act
var result = await ProjectAnalysisHelper.AnalyzeProjectAsync(testProjectPath, _logger);
[Fact]
public async Task AnalyzeProjectAsync_WithPackageReferences_ReturnsPackageInfo()
{
var projectFile = CreateTempProject("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>
</Project>
""");

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

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

[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("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
</Project>
""");

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

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

// Act
var result = await ProjectAnalysisHelper.AnalyzeDependenciesAsync(testProjectPath, _logger);
[Fact]
public async Task AnalyzeDependenciesAsync_WithMinimalProject_ReturnsSuccessJson()
{
var projectFile = CreateTempProject("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
""");

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

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

[Fact]
public async Task AnalyzeDependenciesAsync_WithPackageReferences_ReturnsDependencies()
{
var projectFile = CreateTempProject("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
""");

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

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

[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("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
""");

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

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

// Act
var result = await ProjectAnalysisHelper.ValidateProjectAsync(testProjectPath, _logger);
[Fact]
public async Task ValidateProjectAsync_WithMissingTargetFramework_ReturnsErrors()
{
var projectFile = CreateTempProject("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
</PropertyGroup>
</Project>
""");

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

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

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("""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
""");

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

Check notice

Code scanning / CodeQL

Generic catch clause Note test

Generic catch clause.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

private static string CreateTempProject(string content)
Expand Down
Loading