From 8d175cb13ea179739e368cd04dff5d4e7326b696 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Thu, 18 Jul 2024 12:53:42 +0200 Subject: [PATCH] Fix issues with different provider on issue than on provider --- .../Cake.Frosting.Issues.Recipe.Tests.csproj | 56 ++ .../FakeIssueProvider2.cs | 34 ++ .../PullRequestStatusCalculatorTests.cs | 539 ++++++++++++++++++ .../Cake.Frosting.Issues.Recipe.lutconfig | 6 + .../Cake.Frosting.Issues.Recipe.sln | 14 + .../Properties/ProjectInfo.cs | 3 + .../AzureDevOpsPullRequestSystem.cs | 115 +--- .../PullRequestSystems/PullRequestStatus.cs | 4 + .../PullRequestStatusCalculator.cs | 142 +++++ .../PullRequestStatusExtensions.cs | 17 + .../PullRequestStatusState.cs | 8 + .../PullRequestStatusStateExtensions.cs | 20 + .../Cake.Issues.Tests.ruleset | 13 + 13 files changed, 869 insertions(+), 102 deletions(-) create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/Cake.Frosting.Issues.Recipe.Tests.csproj create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/FakeIssueProvider2.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/PullRequestSystems/PullRequestStatusCalculatorTests.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.lutconfig create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Properties/ProjectInfo.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatus.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusCalculator.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusExtensions.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusState.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusStateExtensions.cs create mode 100644 Cake.Frosting.Issues.Recipe/Cake.Issues.Tests.ruleset diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/Cake.Frosting.Issues.Recipe.Tests.csproj b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/Cake.Frosting.Issues.Recipe.Tests.csproj new file mode 100644 index 00000000..7c0b2d43 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/Cake.Frosting.Issues.Recipe.Tests.csproj @@ -0,0 +1,56 @@ + + + + net6.0;net7.0;net8.0 + latest + true + true + Cake Issues contributors + Copyright © Cake Issues contributors + Cake.Issues + + ..\Cake.Issues.Tests.ruleset + enable + + false + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + runtime; build; native; contentfiles; analyzers + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/FakeIssueProvider2.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/FakeIssueProvider2.cs new file mode 100644 index 00000000..8baafebc --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/FakeIssueProvider2.cs @@ -0,0 +1,34 @@ +namespace Cake.Frosting.Issues.Recipe.Tests; + +using System.Collections.Generic; +using Cake.Core.Diagnostics; + +/// +/// Additional Implementation of a for use in test cases. +/// +internal class FakeIssueProvider2 : FakeIssueProvider +{ + private readonly List issues = []; + + /// + /// Initializes a new instance of the class. + /// + /// The Cake log instance. + public FakeIssueProvider2(ICakeLog log) + : base(log) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Cake log instance. + /// Issues which should be returned by the issue provider. + public FakeIssueProvider2(ICakeLog log, IEnumerable issues) + : base(log, issues) + { + } + + /// + public override string ProviderName => "Fake Issue Provider 2"; +} diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/PullRequestSystems/PullRequestStatusCalculatorTests.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/PullRequestSystems/PullRequestStatusCalculatorTests.cs new file mode 100644 index 00000000..9b227f44 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/PullRequestSystems/PullRequestStatusCalculatorTests.cs @@ -0,0 +1,539 @@ +namespace Cake.Frosting.Issues.Recipe.Tests.PullRequestSystems; + +using Cake.Frosting.Issues.Recipe.PullRequestSystems; +using Microsoft.CodeAnalysis.Sarif; +using Spectre.Console; + +public sealed class PullRequestStatusCalculatorTests +{ + public sealed class TheGetPullRequestStatesMethod + { + public sealed class TheShouldSetPullRequestStatusParameter + { + [Fact] + public void Should_Set_Suceeded_Status_If_Neither_Issues_Nor_Providers_Are_Reported() + { + // Given + var issueProvidersAndRuns = new List<(IIssueProvider, string)>(); + var issues = new List(); + var shouldSetPullRequestStatus = true; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Succeeded); + status.Description.ShouldBe("No issues found"); + } + + [Fact] + public void Should_Set_Succeeded_Status_If_No_Issue_Is_Reported() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var run = "Run 1"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run) + }; + var issues = new List(); + var shouldSetPullRequestStatus = true; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Succeeded); + status.Description.ShouldBe("No issues found"); + } + + [Fact] + public void Should_Set_Failed_Status_If_Issue_And_Provider_Is_Reported() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var run = "Run 1"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run) + }; + var issues = new List + { + IssueBuilder + .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName) + .ForRun(run) + .Create(), + }; + var shouldSetPullRequestStatus = true; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Failed); + status.Description.ShouldBe("Found 1 issues"); + } + + [Fact] + public void Should_Set_Failed_Status_If_Issue_But_No_Provider_Is_Reported() + { + // Given + var issueProvidersAndRuns = new List<(IIssueProvider, string)>(); + var issues = new List + { + IssueBuilder + .NewIssue("Message", "ProviderType", "Provider Name") + .Create(), + }; + var shouldSetPullRequestStatus = true; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Failed); + status.Description.ShouldBe("Found 1 issues"); + } + } + + public sealed class TheShouldSetSeparatePullRequestStatusForEachIssueProviderAndRunParameter + { + [Fact] + public void Should_Set_Succeeded_Status_If_No_Issue_Is_Reported() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, null) + }; + var issues = new List(); + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues-Fake Issue Provider"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Succeeded); + status.Description.ShouldBe("No issues found for Fake Issue Provider"); + } + + [Fact] + public void Should_Set_Failed_Status_If_Issue_Is_Reported() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var run = "Run 1"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run) + }; + var issues = new List + { + IssueBuilder + .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName) + .ForRun(run) + .Create(), + }; + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues-Fake Issue Provider (Run 1)"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Failed); + status.Description.ShouldBe("Found 1 issues for Fake Issue Provider (Run 1)"); + } + + [Fact] + public void Should_Set_Status_For_Each_Provider() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var issueProvider2 = new FakeIssueProvider2(new FakeLog()); + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, null), + new (issueProvider2, null) + }; + var issues = new List + { + IssueBuilder + .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName) + .Create(), + }; + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + result.Count().ShouldBe(2); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Failed && + x.Description == "Found 1 issues for Fake Issue Provider"); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider 2" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Succeeded && + x.Description == "No issues found for Fake Issue Provider 2"); + } + + [Fact] + public void Should_Set_Status_For_Each_Run() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var run1 = "Run 1"; + var run2 = "Run 2"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run1), + new (issueProvider, run2) + }; + var issues = new List + { + IssueBuilder + .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName) + .ForRun(run1) + .Create(), + }; + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + result.Count().ShouldBe(2); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider (Run 1)" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Failed && + x.Description == "Found 1 issues for Fake Issue Provider (Run 1)"); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider (Run 2)" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Succeeded && + x.Description == "No issues found for Fake Issue Provider (Run 2)"); + } + + [Fact] + public void Should_Handle_Issue_Provider_With_Different_Type_But_Same_Run() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var issueProvider2 = new FakeIssueProvider2(new FakeLog()); + var run = "Run 1"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run), + new (issueProvider2, run) + }; + var issues = new List + { + IssueBuilder + .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName) + .ForRun(run) + .Create(), + }; + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + result.Count().ShouldBe(2); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider (Run 1)" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Failed && + x.Description == "Found 1 issues for Fake Issue Provider (Run 1)"); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider 2 (Run 1)" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Succeeded && + x.Description == "No issues found for Fake Issue Provider 2 (Run 1)"); + } + + [Fact] + public void Should_Handle_Issue_Provider_With_Same_Type_But_Different_Name() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var run = "Run 1"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run) + }; + var issues = new List + { + IssueBuilder + .NewIssue("Message", issueProvider.ProviderType, "DifferentIssueProviderName") + .ForRun(run) + .Create(), + }; + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues-DifferentIssueProviderName (Run 1)"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Failed); + status.Description.ShouldBe("Found 1 issues for DifferentIssueProviderName (Run 1)"); + } + + [Fact] + public void Should_Handle_Everything_Together() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var issueProvider2 = new FakeIssueProvider2(new FakeLog()); + var run1 = "Run 1"; + var run2 = "Run 2"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run1), + new (issueProvider, run2), + new (issueProvider2, run2) + }; + var issues = new List + { + IssueBuilder + .NewIssue("Message 1", issueProvider.ProviderType, issueProvider.ProviderName) + .ForRun(run1) + .Create(), + IssueBuilder + .NewIssue("Message 2", issueProvider.ProviderType, issueProvider.ProviderName) + .ForRun(run1) + .Create(), + IssueBuilder + .NewIssue("Message", issueProvider2.ProviderType, "DifferentIssueProviderName") + .ForRun(run2) + .Create(), + IssueBuilder + .NewIssue("Message", "AnotherIssueProvider", "Another Issue Provider") + .Create(), + }; + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier) + .ToList(); + + // Then + result.Count().ShouldBe(4); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider (Run 1)" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Failed && + x.Description == "Found 2 issues for Fake Issue Provider (Run 1)"); + + result.ShouldContain(x => + x.Name == "Issues-Fake Issue Provider (Run 2)" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Succeeded && + x.Description == "No issues found for Fake Issue Provider (Run 2)"); + + result.ShouldContain(x => + x.Name == "Issues-DifferentIssueProviderName (Run 2)" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Failed && + x.Description == "Found 1 issues for DifferentIssueProviderName (Run 2)"); + + result.ShouldContain(x => + x.Name == "Issues-Another Issue Provider" && + x.Genre == "Cake.Issues.Recipe" && + x.State == PullRequestStatusState.Failed && + x.Description == "Found 1 issues for Another Issue Provider"); + } + + [Fact] + public void Should_Consider_Run() + { + // Given + var issueProvider = new FakeIssueProvider(new FakeLog()); + var run = "Run 1"; + var issueProvidersAndRuns = new List<(IIssueProvider, string)>() + { + new (issueProvider, run) + }; + var issues = new List(); + var shouldSetPullRequestStatus = false; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true; + string buildIdentifier = null; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues-Fake Issue Provider (Run 1)"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Succeeded); + status.Description.ShouldBe("No issues found for Fake Issue Provider (Run 1)"); + } + } + + public sealed class TheBuildIdentifierParameter + { + [Fact] + public void Should_Consider_Build_Identifier() + { + // Given + var issueProvidersAndRuns = new List<(IIssueProvider, string)>(); + var issues = new List + { + IssueBuilder + .NewIssue("Message", "ProviderType", "Provider Name") + .Create(), + }; + var shouldSetPullRequestStatus = true; + var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false; + string buildIdentifier = "Build Identifier"; + + // When + var result = + PullRequestStatusCalculator.GetPullRequestStates( + issueProvidersAndRuns, + issues, + shouldSetPullRequestStatus, + shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + buildIdentifier); + + // Then + var status = result.ShouldHaveSingleItem(); + status.Name.ShouldBe("Issues-Build Identifier"); + status.Genre.ShouldBe("Cake.Issues.Recipe"); + status.State.ShouldBe(PullRequestStatusState.Failed); + status.Description.ShouldBe("Found 1 issues in build Build Identifier"); + } + } + } +} \ No newline at end of file diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.lutconfig b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.lutconfig new file mode 100644 index 00000000..ff7fdf3f --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.lutconfig @@ -0,0 +1,6 @@ + + ..\ + true + true + 180000 + \ No newline at end of file diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln index bcf8e62e..e0d77a4c 100644 --- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.2.32526.322 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cake.Frosting.Issues.Recipe", "Cake.Frosting.Issues.Recipe\Cake.Frosting.Issues.Recipe.csproj", "{EF4F2348-04B1-4BDD-87A3-1622845227A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Frosting.Issues.Recipe.Tests", "Cake.Frosting.Issues.Recipe.Tests\Cake.Frosting.Issues.Recipe.Tests.csproj", "{946A333A-2713-4D77-93EE-342573E1F63E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,18 @@ Global {EF4F2348-04B1-4BDD-87A3-1622845227A9}.Release|x64.Build.0 = Release|Any CPU {EF4F2348-04B1-4BDD-87A3-1622845227A9}.Release|x86.ActiveCfg = Release|Any CPU {EF4F2348-04B1-4BDD-87A3-1622845227A9}.Release|x86.Build.0 = Release|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x64.ActiveCfg = Debug|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x64.Build.0 = Debug|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x86.ActiveCfg = Debug|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x86.Build.0 = Debug|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Release|Any CPU.Build.0 = Release|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x64.ActiveCfg = Release|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x64.Build.0 = Release|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x86.ActiveCfg = Release|Any CPU + {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Properties/ProjectInfo.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Properties/ProjectInfo.cs new file mode 100644 index 00000000..1f6ac330 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Properties/ProjectInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Cake.Frosting.Issues.Recipe.Tests")] \ No newline at end of file diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs index 0a9889e0..b586aa4e 100644 --- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs @@ -4,6 +4,7 @@ namespace Cake.Frosting.Issues.Recipe using Cake.AzureDevOps.Repos.PullRequest; using Cake.Common; using Cake.Common.Diagnostics; + using Cake.Frosting.Issues.Recipe.PullRequestSystems; /// /// Support for Azure DevOps / Azure Repository hosted code. @@ -41,55 +42,22 @@ public override void SetPullRequestIssuesState(IIssuesContext context) return; } - // Set status across all issues - if (context.Parameters.PullRequestSystem.ShouldSetPullRequestStatus) + var status = PullRequestStatusCalculator.GetPullRequestStates(context).ToList(); + if (status.Count > 0) { - SetPullRequestStatus( - context, - context.State.Issues, - null); - } - - // Set status for individual issue providers - if (context.Parameters.PullRequestSystem.ShouldSetSeparatePullRequestStatusForEachIssueProviderAndRun) - { - // Determine issue providers and runs from the list of issues providers from which issues were read - // and issue providers and runs of the reported issues. - var issueProvidersAndRuns = - context.State.IssueProvidersAndRuns - .Select(x => - new - { - x.Item1.ProviderName, - Run = x.Item2 - }) - .Union( - context.State.Issues - .GroupBy(x => - new - { - x.ProviderName, - x.Run - }) - .Select(x => - new - { - x.Key.ProviderName, - x.Key.Run - })); + var pullRequestSettings = + new AzureDevOpsPullRequestSettings( + context.State.BuildServer.DetermineRepositoryRemoteUrl(context, context.State.RepositoryRootDirectory), + context.State.BuildServer.DeterminePullRequestId(context).Value, + context.AzureDevOpsAuthenticationOAuth(context.EnvironmentVariable("SYSTEM_ACCESSTOKEN"))); - foreach (var item in issueProvidersAndRuns) + foreach (var item in status) { - var issueProvider = item.ProviderName; - if (!string.IsNullOrEmpty(item.Run)) - { - issueProvider += $" ({item.Run})"; - } + context.Information("Set status {0} to {1}", item.Name, item.State); - SetPullRequestStatus( - context, - context.State.Issues.Where(x => x.ProviderName == item.ProviderName && x.Run == item.Run), - issueProvider); + context.AzureDevOpsSetPullRequestStatus( + pullRequestSettings, + item.ToAzureDevOpsPullRequestStatus()); } } } @@ -106,62 +74,5 @@ public override FileLinkSettings GetFileLinkSettings(IIssuesContext context) context.State.CommitId, rootPath.FullPath); } - - /// - /// Reports a status to the pull request of the current build. - /// - /// The Cake context. - /// Issues for which the status should be reported. - /// Identifier for the issues passed in . - private static void SetPullRequestStatus( - IIssuesContext context, - IEnumerable issues, - string issueIdentifier) - { - var pullRequestSettings = - new AzureDevOpsPullRequestSettings( - context.State.BuildServer.DetermineRepositoryRemoteUrl(context, context.State.RepositoryRootDirectory), - context.State.BuildServer.DeterminePullRequestId(context).Value, - context.AzureDevOpsAuthenticationOAuth(context.EnvironmentVariable("SYSTEM_ACCESSTOKEN"))); - - var issuesList = issues.ToList(); - - var pullRequestStatusName = "Issues"; - var pullRequestDescriptionIfIssues = $"Found {issuesList.Count} issues"; - var pullRequestDescriptionIfNoIssues = "No issues found"; - - if (!string.IsNullOrWhiteSpace(issueIdentifier)) - { - pullRequestStatusName += $"-{issueIdentifier}"; - pullRequestDescriptionIfIssues += $" for {issueIdentifier}"; - pullRequestDescriptionIfNoIssues += $" for {issueIdentifier}"; - } - - if (!string.IsNullOrWhiteSpace(context.Parameters.BuildIdentifier)) - { - pullRequestStatusName += $"-{context.Parameters.BuildIdentifier}"; - pullRequestDescriptionIfIssues += $" in build {context.Parameters.BuildIdentifier}"; - pullRequestDescriptionIfNoIssues += $" in build {context.Parameters.BuildIdentifier}"; - } - - var state = - issuesList.Count != 0 ? - AzureDevOpsPullRequestStatusState.Failed : - AzureDevOpsPullRequestStatusState.Succeeded; - - context.Information("Set status {0} to {1}", pullRequestStatusName, state); - - var pullRequestStatus = - new AzureDevOpsPullRequestStatus(pullRequestStatusName) - { - Genre = "Cake.Issues.Recipe", - State = state, - Description = issuesList.Count != 0 ? pullRequestDescriptionIfIssues : pullRequestDescriptionIfNoIssues - }; - - context.AzureDevOpsSetPullRequestStatus( - pullRequestSettings, - pullRequestStatus); - } } } \ No newline at end of file diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatus.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatus.cs new file mode 100644 index 00000000..fa0a0fb8 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatus.cs @@ -0,0 +1,4 @@ +namespace Cake.Frosting.Issues.Recipe.PullRequestSystems +{ + internal record PullRequestStatus(string Name, string Genre, PullRequestStatusState State, string Description); +} diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusCalculator.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusCalculator.cs new file mode 100644 index 00000000..7a3719af --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusCalculator.cs @@ -0,0 +1,142 @@ +namespace Cake.Frosting.Issues.Recipe.PullRequestSystems +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Class to calculate the status which should be reported to the pull request. + /// + internal class PullRequestStatusCalculator + { + public static IEnumerable GetPullRequestStates(IIssuesContext context) + { + return GetPullRequestStates( + context.State.IssueProvidersAndRuns, + context.State.Issues, + context.Parameters.PullRequestSystem.ShouldSetPullRequestStatus, + context.Parameters.PullRequestSystem.ShouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + context.Parameters.BuildIdentifier); + } + + public static IEnumerable GetPullRequestStates( + IList<(IIssueProvider, string)> issueProvidersAndRuns, + IEnumerable issues, + bool shouldSetPullRequestStatus, + bool shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun, + string buildIdentifier) + { + var result = new List(); + + // Set status across all issues + if (shouldSetPullRequestStatus) + { + result.Add(PullRequestStatusCalculator.CreatePullRequestState(issues, null, buildIdentifier)); + } + + // Set status for individual issue providers + if (shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun) + { + // Determine issue providers and runs from the list of issues providers from which issues were read + // and issue providers and runs of the reported issues. + var comparer = new IssueProviderAndRunComparer(); + var pullrequestStateIssues = + issues + .GroupBy(x => + new + { + x.ProviderType, + x.ProviderName, + x.Run + }) + .Select(x => + new PullRequestStateIssue( + x.Key.ProviderType, + x.Key.ProviderName, + x.Key.Run)) + .Union( + issueProvidersAndRuns + .Select(x => + new PullRequestStateIssue( + x.Item1.ProviderType, + x.Item1.ProviderName, + x.Item2)), comparer); + + foreach (var item in pullrequestStateIssues) + { + var issueProvider = item.ProviderName; + if (!string.IsNullOrEmpty(item.Run)) + { + issueProvider += $" ({item.Run})"; + } + + result.Add( + PullRequestStatusCalculator.CreatePullRequestState( + issues.Where(x => x.ProviderName == item.ProviderName && x.Run == item.Run), + issueProvider, + buildIdentifier)); + } + } + + return result; + } + + private record PullRequestStateIssue(string ProviderType, string ProviderName, string Run); + + private class IssueProviderAndRunComparer : IEqualityComparer + { + public bool Equals(PullRequestStateIssue x, PullRequestStateIssue y) + { + return x.ProviderType == y.ProviderType && x.Run == y.Run; + } + + public int GetHashCode(PullRequestStateIssue obj) + { + var result = obj.ProviderType.GetHashCode(StringComparison.OrdinalIgnoreCase); + + if (obj.Run != null) + { + result ^= obj.Run.GetHashCode(StringComparison.OrdinalIgnoreCase); + } + + return result; + } + } + + private static PullRequestStatus CreatePullRequestState( + IEnumerable issues, + string issueIdentifier, + string buildIdentifier) + { + var issuesList = issues.ToList(); + + var pullRequestStatusName = "Issues"; + var pullRequestDescriptionIfIssues = $"Found {issuesList.Count} issues"; + var pullRequestDescriptionIfNoIssues = "No issues found"; + + if (!string.IsNullOrWhiteSpace(issueIdentifier)) + { + pullRequestStatusName += $"-{issueIdentifier}"; + pullRequestDescriptionIfIssues += $" for {issueIdentifier}"; + pullRequestDescriptionIfNoIssues += $" for {issueIdentifier}"; + } + + if (!string.IsNullOrWhiteSpace(buildIdentifier)) + { + pullRequestStatusName += $"-{buildIdentifier}"; + pullRequestDescriptionIfIssues += $" in build {buildIdentifier}"; + pullRequestDescriptionIfNoIssues += $" in build {buildIdentifier}"; + } + + var state = + issuesList.Count != 0 ? + PullRequestStatusState.Failed : + PullRequestStatusState.Succeeded; + + return new PullRequestStatus( + pullRequestStatusName, + "Cake.Issues.Recipe", + state, + issuesList.Count != 0 ? pullRequestDescriptionIfIssues : pullRequestDescriptionIfNoIssues); + } + } +} diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusExtensions.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusExtensions.cs new file mode 100644 index 00000000..67774686 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusExtensions.cs @@ -0,0 +1,17 @@ +namespace Cake.Frosting.Issues.Recipe.PullRequestSystems +{ + using Cake.AzureDevOps.Repos.PullRequest; + + internal static class PullRequestStatusExtensions + { + public static AzureDevOpsPullRequestStatus ToAzureDevOpsPullRequestStatus(this PullRequestStatus status) + { + return new AzureDevOpsPullRequestStatus(status.Name) + { + Genre = status.Genre, + State = status.State.ToAzureDevOpsPullRequestStatusState(), + Description = status.Description + }; + } + } +} diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusState.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusState.cs new file mode 100644 index 00000000..6072e7d4 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusState.cs @@ -0,0 +1,8 @@ +namespace Cake.Frosting.Issues.Recipe.PullRequestSystems +{ + internal enum PullRequestStatusState + { + Succeeded, + Failed + } +} diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusStateExtensions.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusStateExtensions.cs new file mode 100644 index 00000000..4bb81e69 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusStateExtensions.cs @@ -0,0 +1,20 @@ +namespace Cake.Frosting.Issues.Recipe.PullRequestSystems +{ + using Cake.AzureDevOps.Repos.PullRequest; + + internal static class PullRequestStatusStateExtensions + { + public static AzureDevOpsPullRequestStatusState ToAzureDevOpsPullRequestStatusState(this PullRequestStatusState status) + { + switch (status) + { + case PullRequestStatusState.Succeeded: + return AzureDevOpsPullRequestStatusState.Succeeded; + case PullRequestStatusState.Failed: + return AzureDevOpsPullRequestStatusState.Failed; + default: + return AzureDevOpsPullRequestStatusState.Succeeded; + } + } + } +} diff --git a/Cake.Frosting.Issues.Recipe/Cake.Issues.Tests.ruleset b/Cake.Frosting.Issues.Recipe/Cake.Issues.Tests.ruleset new file mode 100644 index 00000000..ce420cdb --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Issues.Tests.ruleset @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file