From 44250f5f29f4f69f2151bc511e9fdade23bf7703 Mon Sep 17 00:00:00 2001 From: jdickson Date: Sat, 14 Dec 2024 16:31:29 +0700 Subject: [PATCH] fix most of teh tests and share solution creation boiler plate between tests --- .../Agoda.CodeCompass.MSBuild.Tests.csproj | 7 +- .../SarifConversionTests.cs | 38 ++-- src/Agoda.CodeCompass.MSBuild.Tests/Setup.cs | 80 ++++++++ .../TechDebtSarifTaskTests.cs | 171 ++++++++++++++++++ .../input.json | 30 +++ src/Agoda.CodeCompass.MSBuild/Class1.cs | 15 -- .../SarifReporter.cs | 19 +- .../TechDebtMetadata.cs | 103 +---------- .../TechDebtReportTask.cs | 28 ++- 9 files changed, 352 insertions(+), 139 deletions(-) create mode 100644 src/Agoda.CodeCompass.MSBuild.Tests/Setup.cs create mode 100644 src/Agoda.CodeCompass.MSBuild.Tests/TechDebtSarifTaskTests.cs create mode 100644 src/Agoda.CodeCompass.MSBuild.Tests/input.json delete mode 100644 src/Agoda.CodeCompass.MSBuild/Class1.cs diff --git a/src/Agoda.CodeCompass.MSBuild.Tests/Agoda.CodeCompass.MSBuild.Tests.csproj b/src/Agoda.CodeCompass.MSBuild.Tests/Agoda.CodeCompass.MSBuild.Tests.csproj index 959a579..5a6e22f 100644 --- a/src/Agoda.CodeCompass.MSBuild.Tests/Agoda.CodeCompass.MSBuild.Tests.csproj +++ b/src/Agoda.CodeCompass.MSBuild.Tests/Agoda.CodeCompass.MSBuild.Tests.csproj @@ -23,9 +23,12 @@ PreserveNewest - - + + + + + diff --git a/src/Agoda.CodeCompass.MSBuild.Tests/SarifConversionTests.cs b/src/Agoda.CodeCompass.MSBuild.Tests/SarifConversionTests.cs index 8a7ef49..3ec71d2 100644 --- a/src/Agoda.CodeCompass.MSBuild.Tests/SarifConversionTests.cs +++ b/src/Agoda.CodeCompass.MSBuild.Tests/SarifConversionTests.cs @@ -1,5 +1,4 @@ -using System.Text.Json; -using Agoda.CodeCompass.MSBuild.Sarif; +using Agoda.CodeCompass.MSBuild.Sarif; using Microsoft.Build.Framework; using Microsoft.VisualStudio.TestPlatform.Utilities; using Newtonsoft.Json.Serialization; @@ -18,30 +17,36 @@ public class SarifConversionTests private readonly string _writeSarifPath = "TestData/write.sarif"; private readonly string _sampleSarifPath = "TestData/sample.sarif"; private readonly IBuildEngine _buildEngine = Substitute.For(); - private JsonSerializerSettings _jsonSettings = new JsonSerializerSettings + private JsonSerializerSettings _jsonSettings = new() { ContractResolver = new CamelCasePropertyNamesContractResolver(), Error = HandleDeserializationError, Formatting = Formatting.Indented, }; + public SarifConversionTests() + { + _tempSolutionDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_tempSolutionDir); + } + private string _tempSolutionDir; + [Test] public async Task ConvertSarif_WithValidInput_ShouldAddTechDebtProperties() { + SetupTests.SetupSolutionAndProject(_tempSolutionDir); var outfile = "TestData/" + Guid.NewGuid(); // Arrange var task = new TechDebtSarifTask { InputPath = _sampleSarifPath, OutputPath = outfile, - BuildEngine = _buildEngine + BuildEngine = _buildEngine, + SolutionPath = _tempSolutionDir }; // Act - var result = task.Execute(); - - // Assert - result.ShouldBeTrue(); + task.Execute().ShouldBeTrue(); var jsonSettings = new JsonSerializerSettings { @@ -67,17 +72,17 @@ public async Task ConvertSarif_WithValidInput_ShouldAddTechDebtProperties() [Test] public async Task ConvertSarif_WithV1FromTestData_ShouldHave1Violation() { + SetupTests.SetupSolutionAndProject(_tempSolutionDir); var outfile = "TestData/" + Guid.NewGuid(); var task = new TechDebtSarifTask { InputPath = "TestData/v1.sarif", OutputPath = outfile, - BuildEngine = _buildEngine + BuildEngine = _buildEngine, + SolutionPath = _tempSolutionDir }; - var result = task.Execute(); - - result.ShouldBeTrue(); + task.Execute().ShouldBeTrue(); var outputJson = await File.ReadAllTextAsync(outfile); var output = JsonConvert.DeserializeObject(outputJson, _jsonSettings); @@ -100,6 +105,7 @@ private static void HandleDeserializationError(object sender, ErrorEventArgs err [Test] public async Task ConvertSarif_WithMultipleRules_ShouldPreserveRuleMetadata() { + SetupTests.SetupSolutionAndProject(_tempSolutionDir); // Arrange var sarif = new SarifReport { @@ -116,18 +122,20 @@ public async Task ConvertSarif_WithMultipleRules_ShouldPreserveRuleMetadata() } }; + var outfile = "TestData/" + Guid.NewGuid(); await File.WriteAllTextAsync(_writeSarifPath, JsonConvert.SerializeObject(sarif, _jsonSettings)); - var outfile = "TestData/" + Guid.NewGuid(); + var task = new TechDebtSarifTask { InputPath = _writeSarifPath, OutputPath = outfile, - BuildEngine = _buildEngine + BuildEngine = _buildEngine, + SolutionPath = _tempSolutionDir }; // Act - task.Execute(); + task.Execute().ShouldBeTrue(); // Assert var outputJson = await File.ReadAllTextAsync(outfile); diff --git a/src/Agoda.CodeCompass.MSBuild.Tests/Setup.cs b/src/Agoda.CodeCompass.MSBuild.Tests/Setup.cs new file mode 100644 index 0000000..47197a1 --- /dev/null +++ b/src/Agoda.CodeCompass.MSBuild.Tests/Setup.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Agoda.CodeCompass.MSBuild.Tests; + +public static class SetupTests +{ + public static void SetupSolutionAndProject(string tempSolutionDir) + { + // Create a minimal solution structure + var projectDir = Path.Combine(tempSolutionDir, "TestProject"); + Directory.CreateDirectory(projectDir); + + // Create project file with explicit SDK reference + var projectPath = Path.Combine(projectDir, "TestProject.csproj"); + File.WriteAllText(projectPath, @" + + + net8.0 + library + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + +"); + + // Create a test file with a diagnostic that should be reported + var sourcePath = Path.Combine(projectDir, "Test.cs"); + File.WriteAllText(sourcePath, @" +using System; + +public class Test +{ + // CS0649: Field is never assigned to + private readonly string _unused; + + public void Method() + { + // CS0219: Variable is assigned but its value is never used + int unused = 1; + } +}"); + // Create solution file that references the project + var projectGuid = Guid.NewGuid().ToString("B").ToUpper(); + var solutionPath = Path.Combine(tempSolutionDir, "test.sln"); + File.WriteAllText(solutionPath, $@" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""TestProject"", ""TestProject\TestProject.csproj"", ""{projectGuid}"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {projectGuid}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {projectGuid}.Debug|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal"); + } +} \ No newline at end of file diff --git a/src/Agoda.CodeCompass.MSBuild.Tests/TechDebtSarifTaskTests.cs b/src/Agoda.CodeCompass.MSBuild.Tests/TechDebtSarifTaskTests.cs new file mode 100644 index 0000000..05c5679 --- /dev/null +++ b/src/Agoda.CodeCompass.MSBuild.Tests/TechDebtSarifTaskTests.cs @@ -0,0 +1,171 @@ +using Agoda.CodeCompass.Data; +using Agoda.CodeCompass.MSBuild; +using Microsoft.Build.Framework; +using Microsoft.CodeAnalysis.MSBuild; +using NSubstitute; +using NUnit.Framework; +using Shouldly; +using System.Reflection; +using System.Text.Json; +using Agoda.CodeCompass.MSBuild.Sarif; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json; +using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; +using JsonSerializer = Newtonsoft.Json.JsonSerializer; + +namespace Agoda.CodeCompass.MSBuild.Tests; + +[TestFixture] +public class TechDebtSarifTaskTests +{ + private string _tempInputPath; + private string _tempOutputPath; + private string _tempSolutionDir; + private IBuildEngine _buildEngine; + private MSBuildWorkspace _workspace; + private JsonSerializerSettings _jsonSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Error = HandleDeserializationError, + Formatting = Formatting.Indented, + }; + + private static void HandleDeserializationError(object? sender, ErrorEventArgs e) + { + Console.WriteLine(e.ErrorContext.Error.ToString()); + } + + static TechDebtSarifTaskTests() + { + // Register assembly resolution handler for Microsoft.Build assemblies + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => + { + var requestedAssembly = new AssemblyName(args.Name); + + // If it's looking for an older version of Microsoft.Build.Framework, redirect to the new one + if (requestedAssembly.Name == "Microsoft.Build.Framework" && requestedAssembly.Version.Major == 15) + { + return Assembly.Load("Microsoft.Build.Framework, Version=17.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + } + + // If it's looking for an older version of Microsoft.Build, redirect to the new one + if (requestedAssembly.Name == "Microsoft.Build" && requestedAssembly.Version.Major == 15) + { + return Assembly.Load("Microsoft.Build, Version=17.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + } + + return null; + }; + } + [SetUp] + public void Setup() + { + // Create temporary directories and files + _tempSolutionDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_tempSolutionDir); + + // Set up temporary paths for input/output + _tempInputPath = Path.Combine(_tempSolutionDir, "input.sarif"); + _tempOutputPath = Path.Combine(_tempSolutionDir, "output.sarif"); + + // Set up substitute build engine + _buildEngine = Substitute.For(); + + // Set environment variable + Environment.SetEnvironmentVariable("SolutionDir", _tempSolutionDir); + + // Configure MSBuildWorkspace + var properties = new Dictionary + { + { "Configuration", "Debug" }, + { "Platform", "AnyCPU" } + }; + + _workspace = MSBuildWorkspace.Create(properties); + + // Subscribe to workspace failed events for debugging + _workspace.WorkspaceFailed += (sender, args) => + { + Console.WriteLine($"Workspace failure: {args.Diagnostic.Message}"); + }; + } + + [TearDown] + public void TearDown() + { + _workspace?.Dispose(); + + if (Directory.Exists(_tempSolutionDir)) + { + Directory.Delete(_tempSolutionDir, true); + } + + Environment.SetEnvironmentVariable("SolutionDir", null); + } + + [Test] + public void Execute_ShouldUpdateTechDebtMetadata() + { + // Arrange + SetupTests.SetupSolutionAndProject(_tempSolutionDir); + + var minimalSarifContent = @"{ + ""$schema"": ""https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"", + ""version"": ""2.1.0"", + ""runs"": [ + { + ""tool"": { + ""driver"": { + ""name"": ""Test Tool"", + ""rules"": [ + { + ""id"": ""CS0649"", + ""properties"": { + ""techDebtMinutes"": ""30"", + ""category"": ""TestCategory"", + ""priority"": ""High"" + } + } + ] + } + }, + ""results"": [ + { + ""ruleId"": ""CS0649"", + ""ruleId"": ""AG0005"" + } + ] + } + ] + }"; + + File.WriteAllText(_tempInputPath, minimalSarifContent); + + var task = new TechDebtSarifTask + { + BuildEngine = _buildEngine, + InputPath = _tempInputPath, + OutputPath = _tempOutputPath, + SolutionPath = _tempSolutionDir + }; + + task.Execute().ShouldBeTrue(); + + File.Exists(_tempOutputPath).ShouldBeTrue(); + _buildEngine.DidNotReceive().LogErrorEvent(Arg.Any()); + + var outputContent = File.ReadAllText(_tempOutputPath); + var outputDoc = JsonConvert.DeserializeObject(outputContent, _jsonSettings); + + // Verify the tech debt info was updated + var techDebtInfo = outputDoc.Runs[0].Results[0].Properties.TechDebt; + techDebtInfo.ShouldNotBeNull(); + techDebtInfo.Minutes.ShouldBe(10); + techDebtInfo.Category.ShouldBe("Compiler"); + techDebtInfo.Priority.ShouldBe("Low"); + // Verify the tech debt info was updated + techDebtInfo = outputDoc.Runs[0].Tool.Driver.Rules.FirstOrDefault(x => x.Id == "AG0005").Properties.TechDebt; + techDebtInfo.ShouldNotBeNull(); + techDebtInfo.Minutes.ShouldBe(10); + } +} \ No newline at end of file diff --git a/src/Agoda.CodeCompass.MSBuild.Tests/input.json b/src/Agoda.CodeCompass.MSBuild.Tests/input.json new file mode 100644 index 0000000..0ae0415 --- /dev/null +++ b/src/Agoda.CodeCompass.MSBuild.Tests/input.json @@ -0,0 +1,30 @@ +{ + "schema": "http://json.schemastore.org/sarif-1.0.0", + "version": "1.0.0", + "runs": [ + { + "results": [ + { + "ruleId": "CS0649", + "level": "warning", + "message": "Test method names must follow convention", + "locations": [ + { + "resultFile": { + "uri": "file:///C:/source/gitlab.agodadev.io/full-stack/templates/supply-extranet-template/src/AspnetWebApi/src/Agoda.SupplyExtranetTemplate.UnitTests/ExampleTestFixture.cs", + "region": { + "startLine": 10, + "startColumn": 17, + "endLine": 10, + "endColumn": 54 + } + } + } + ], + "properties": { + } + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Agoda.CodeCompass.MSBuild/Class1.cs b/src/Agoda.CodeCompass.MSBuild/Class1.cs deleted file mode 100644 index 97f8a6d..0000000 --- a/src/Agoda.CodeCompass.MSBuild/Class1.cs +++ /dev/null @@ -1,15 +0,0 @@ -public void ConfigureServices(IServiceCollection services) -{ - services.AddDataServices().AddBusinessServices().AddInfrastructureServices(); - // ... more method calls -} - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddDataServices(this IServiceCollection services) - { - services.AddSingleton(); - services.AddTransient(); // ... more registrations return services; - } - // ... more extension methods -} \ No newline at end of file diff --git a/src/Agoda.CodeCompass.MSBuild/SarifReporter.cs b/src/Agoda.CodeCompass.MSBuild/SarifReporter.cs index ab76b8f..1b15fd5 100644 --- a/src/Agoda.CodeCompass.MSBuild/SarifReporter.cs +++ b/src/Agoda.CodeCompass.MSBuild/SarifReporter.cs @@ -16,6 +16,7 @@ public class SarifReporter Error = HandleDeserializationError, Formatting = Newtonsoft.Json.Formatting.Indented, }; + private static void HandleDeserializationError(object sender, ErrorEventArgs errorArgs) { // Log the error but don't throw it @@ -23,24 +24,24 @@ private static void HandleDeserializationError(object sender, ErrorEventArgs err Console.WriteLine($"Warning during SARIF processing: {currentError}"); errorArgs.ErrorContext.Handled = true; } + public static string AddTechDebtToSarif(string sarifContent) { - // Detect version var jObject = JObject.Parse(sarifContent); var version = jObject["version"]?.ToString(); return version switch { - "1.0.0" => AddTechDebtToSarifV1(sarifContent, _jsonSettings), - "2.1.0" => AddTechDebtToSarifV2(sarifContent, _jsonSettings), + "1.0.0" => AddTechDebtToSarifV1(sarifContent), + "2.1.0" => AddTechDebtToSarifV2(sarifContent), _ => throw new NotSupportedException($"Unsupported SARIF version: {version}") }; } - private static string AddTechDebtToSarifV1(string sarifContent, JsonSerializerSettings jsonSettings) + private static string AddTechDebtToSarifV1(string sarifContent) { - var report = JsonConvert.DeserializeObject(sarifContent, jsonSettings); + var report = JsonConvert.DeserializeObject(sarifContent, _jsonSettings); // Add tech debt properties to results for V1 foreach (var run in report.Runs) @@ -51,12 +52,12 @@ private static string AddTechDebtToSarifV1(string sarifContent, JsonSerializerSe } } - return JsonConvert.SerializeObject(report, jsonSettings); + return JsonConvert.SerializeObject(report, _jsonSettings); } - private static string AddTechDebtToSarifV2(string sarifContent, JsonSerializerSettings jsonSettings) + private static string AddTechDebtToSarifV2(string sarifContent) { - var report = JsonConvert.DeserializeObject(sarifContent, jsonSettings); + var report = JsonConvert.DeserializeObject(sarifContent, _jsonSettings); // Add tech debt properties to rules if (report.Runs?.FirstOrDefault()?.Tool?.Driver?.Rules != null) @@ -76,7 +77,7 @@ private static string AddTechDebtToSarifV2(string sarifContent, JsonSerializerSe } } - return JsonConvert.SerializeObject(report, jsonSettings); + return JsonConvert.SerializeObject(report, _jsonSettings); } private static TechDebtProperties GetTechDebtProperties(string ruleId) diff --git a/src/Agoda.CodeCompass.MSBuild/TechDebtMetadata.cs b/src/Agoda.CodeCompass.MSBuild/TechDebtMetadata.cs index 48190ae..0368b42 100644 --- a/src/Agoda.CodeCompass.MSBuild/TechDebtMetadata.cs +++ b/src/Agoda.CodeCompass.MSBuild/TechDebtMetadata.cs @@ -26,7 +26,7 @@ public static class TechDebtMetadata // Agoda Rules ["AG0002"] = new TechDebtInfo { Minutes = 15, Category = "AgodaSpecific", Priority = "Medium", Rationale = "Agoda-specific implementation issue", Recommendation = "Follow Agoda's implementation guidelines" }, ["AG0003"] = new TechDebtInfo { Minutes = 20, Category = "AgodaSpecific", Priority = "Medium", Rationale = "Agoda-specific implementation issue", Recommendation = "Follow Agoda's implementation guidelines" }, - ["AG0005"] = new TechDebtInfo { Minutes = 25, Category = "AgodaSpecific", Priority = "High", Rationale = "Agoda-specific architecture violation", Recommendation = "Restructure according to architecture guidelines" }, + ["AG0005"] = new TechDebtInfo { Minutes = 1, Category = "AgodaSpecific", Priority = "High", Rationale = "Agoda-specific architecture violation", Recommendation = "Restructure according to architecture guidelines" }, ["AG0009"] = new TechDebtInfo { Minutes = 15, Category = "AgodaSpecific", Priority = "Medium", Rationale = "Agoda-specific naming violation", Recommendation = "Follow naming conventions" }, ["AG0010"] = new TechDebtInfo { Minutes = 20, Category = "AgodaSpecific", Priority = "Medium", Rationale = "Agoda-specific implementation issue", Recommendation = "Follow implementation guidelines" }, ["AG0011"] = new TechDebtInfo { Minutes = 15, Category = "AgodaSpecific", Priority = "Medium", Rationale = "Agoda-specific implementation issue", Recommendation = "Follow implementation guidelines" }, @@ -311,91 +311,7 @@ public static class TechDebtMetadata }; - private static List GetProjectAnalyzers(string projectPath) - { - var analyzerPaths = GetAnalyzerPaths(projectPath); - var analyzers = new List(); - - foreach (var analyzerPath in analyzerPaths) - { - try - { - var assembly = Assembly.LoadFrom(analyzerPath); - - var analyzerTypes = assembly.GetTypes() - .Where(t => !t.IsAbstract && !t.IsInterface && - typeof(DiagnosticAnalyzer).IsAssignableFrom(t)); - - foreach (var analyzerType in analyzerTypes) - { - if (Activator.CreateInstance(analyzerType) is DiagnosticAnalyzer analyzer) - { - analyzers.Add(analyzer); - } - } - } - catch (Exception ex) - { - // Log but continue with other analyzers - Console.WriteLine($"Error loading analyzer {analyzerPath}: {ex.Message}"); - } - } - - return analyzers; - } - - private static List GetAnalyzerPaths(string projectPath) - { - var analyzerPaths = new List(); - var projectDir = Path.GetDirectoryName(projectPath); - - // Load project file - var projectXml = XDocument.Load(projectPath); - var ns = projectXml.Root.GetDefaultNamespace(); - - // Get direct analyzer references - var analyzerReferences = projectXml.Descendants(ns + "Analyzer") - .Select(x => x.Attribute("Include")?.Value) - .Where(x => !string.IsNullOrEmpty(x)); - analyzerPaths.AddRange(analyzerReferences); - - // Get package references - var packageReferences = projectXml.Descendants(ns + "PackageReference") - .Select(x => new - { - Id = x.Attribute("Include")?.Value, - Version = x.Attribute("Version")?.Value - }) - .Where(x => !string.IsNullOrEmpty(x.Id) && !string.IsNullOrEmpty(x.Version)); - - foreach (var package in packageReferences) - { - var packagePath = Path.Combine( - projectDir, - "obj", - "project.nuget.cache"); - - if (File.Exists(packagePath)) - { - var nugetCache = XDocument.Load(packagePath); - var packageFolder = nugetCache.Descendants("PackageFolder") - .FirstOrDefault(x => x.Attribute("id")?.Value == package.Id && - x.Attribute("version")?.Value == package.Version); - - if (packageFolder != null) - { - var analyzerDir = Path.Combine(packageFolder.Value, "analyzers", "dotnet"); - if (Directory.Exists(analyzerDir)) - { - analyzerPaths.AddRange(Directory.GetFiles(analyzerDir, "*.dll")); - } - } - } - } - - return analyzerPaths; - } private static string GetPriorityFromSeverity(DiagnosticSeverity severity) { return severity switch @@ -421,14 +337,7 @@ private static string GetPriorityFromSeverity(DiagnosticSeverity severity) return null; } - - public static IEnumerable GetAllRuleIds() - { - return PredefinedMetadata.Keys - .Concat(AnalyzerMetadata.Keys) - .Distinct(); - } - + public static void UpdateMetadataFromDiagnostics(IEnumerable diagnostics) { foreach (var diagnostic in diagnostics) @@ -444,7 +353,7 @@ public static void UpdateMetadataFromDiagnostics(IEnumerable diagnos // Try to get tech debt minutes from properties int minutes = 15; // Default value - if (properties.TryGetValue("techDebtMinutes", out var techDebtMinutesStr)) + if (properties.TryGetValue("TechDebtInMinutes", out var techDebtMinutesStr)) { if (!int.TryParse(techDebtMinutesStr, out minutes)) { @@ -454,7 +363,7 @@ public static void UpdateMetadataFromDiagnostics(IEnumerable diagnos // Determine category based on descriptor or fallback to a default string category = "BestPractices"; // Default category - if (properties.TryGetValue("category", out var categoryFromProps)) + if (properties.TryGetValue("Category", out var categoryFromProps)) { category = categoryFromProps; } @@ -468,14 +377,14 @@ public static void UpdateMetadataFromDiagnostics(IEnumerable diagnos // Create rationale from diagnostic description string rationale = descriptor.Description.ToString(); - if (properties.TryGetValue("rationale", out var rationaleFromProps)) + if (properties.TryGetValue("Rationale", out var rationaleFromProps)) { rationale = rationaleFromProps; } // Get recommendation from help link or message string recommendation = descriptor.HelpLinkUri ?? descriptor.MessageFormat.ToString(); - if (properties.TryGetValue("recommendation", out var recommendationFromProps)) + if (properties.TryGetValue("Recommendation", out var recommendationFromProps)) { recommendation = recommendationFromProps; } diff --git a/src/Agoda.CodeCompass.MSBuild/TechDebtReportTask.cs b/src/Agoda.CodeCompass.MSBuild/TechDebtReportTask.cs index 63268a9..d83db51 100644 --- a/src/Agoda.CodeCompass.MSBuild/TechDebtReportTask.cs +++ b/src/Agoda.CodeCompass.MSBuild/TechDebtReportTask.cs @@ -12,12 +12,38 @@ using Task = Microsoft.Build.Utilities.Task; namespace Agoda.CodeCompass.MSBuild; + public class TechDebtSarifTask : Task { [Required] public string InputPath { get; set; } = string.Empty; + [Required] public string OutputPath { get; set; } = string.Empty; + + public string SolutionPath { get; set; } = string.Empty; + + private string ResolveSolutionPath() + { + if (!string.IsNullOrEmpty(SolutionPath)) + { + return SolutionPath; + } + + var solutionDir = Environment.GetEnvironmentVariable("SolutionDir"); + if (string.IsNullOrEmpty(solutionDir)) + { + throw new InvalidOperationException("Neither SolutionPath property nor SolutionDir environment variable is set."); + } + + var solutionFile = Directory.EnumerateFiles(solutionDir, "*.sln").FirstOrDefault(); + if (solutionFile == null) + { + throw new FileNotFoundException("Solution file not found.", solutionDir); + } + + return solutionFile; + } public override bool Execute() { @@ -25,7 +51,7 @@ public override bool Execute() try { var inputSarif = File.ReadAllText(InputPath); - var solutionPath = Environment.GetEnvironmentVariable("SolutionDir"); + var solutionPath = ResolveSolutionPath(); if (string.IsNullOrEmpty(solutionPath)) { throw new InvalidOperationException("SolutionDir is not set.");