diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..9cc496505 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.cs diff=csharp diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index da37e81da..44482aa47 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # These owners will be the default owners for everything in the repo and # will be requested for review when someone opens a pull request. -* @pascalberger @christianbumann @x-jokay @silanosa @georgesgoetz \ No newline at end of file +* @cake-contrib/team-bbt @x-jokay \ No newline at end of file diff --git a/nuspec/nuget/Cake.Issues.Testing.nuspec b/nuspec/nuget/Cake.Issues.Testing.nuspec index 519258325..aaf4fcd4e 100644 --- a/nuspec/nuget/Cake.Issues.Testing.nuspec +++ b/nuspec/nuget/Cake.Issues.Testing.nuspec @@ -17,7 +17,7 @@ Common helpers for testing add-ins based on Cake.Issues Copyright © BBT Software AG and contributors Cake Script Cake-Issues Issues Testing - https://github.com/cake-contrib/Cake.Issues/releases/tag/0.8.1 + https://github.com/cake-contrib/Cake.Issues/releases/tag/0.9.0 diff --git a/nuspec/nuget/Cake.Issues.nuspec b/nuspec/nuget/Cake.Issues.nuspec index 8287bab58..bafece048 100644 --- a/nuspec/nuget/Cake.Issues.nuspec +++ b/nuspec/nuget/Cake.Issues.nuspec @@ -24,7 +24,7 @@ See the Project Site for an overview of the whole ecosystem of addins for workin Copyright © BBT Software AG and contributors Cake Script Cake-Issues CodeAnalysis Linting Issues - https://github.com/cake-contrib/Cake.Issues/releases/tag/0.8.1 + https://github.com/cake-contrib/Cake.Issues/releases/tag/0.9.0 diff --git a/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs b/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs index c71f7f198..69b1799ac 100644 --- a/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs +++ b/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs @@ -19,7 +19,7 @@ public abstract class BaseIssueProviderFixture protected BaseIssueProviderFixture() { this.Log = new FakeLog { Verbosity = Verbosity.Normal }; - this.RepositorySettings = new RepositorySettings(@"c:\repo"); + this.ReadIssuesSettings = new ReadIssuesSettings(@"c:\repo"); } /// @@ -30,7 +30,7 @@ protected BaseIssueProviderFixture() /// /// Gets or sets the repository settings. /// - public RepositorySettings RepositorySettings { get; set; } + public ReadIssuesSettings ReadIssuesSettings { get; set; } /// /// Calls . @@ -67,12 +67,12 @@ private T CreateIssueProvider() typeof(T), this.GetCreateIssueProviderArguments().ToArray()); - if (this.RepositorySettings == null) + if (this.ReadIssuesSettings == null) { - throw new InvalidOperationException("No repository settings set."); + throw new InvalidOperationException("No settings for reading issues set."); } - provider.Initialize(this.RepositorySettings); + provider.Initialize(this.ReadIssuesSettings); return provider; } } diff --git a/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj b/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj index 4347c3be9..ef03c32ac 100644 --- a/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj +++ b/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj @@ -26,7 +26,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs b/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs index 5a7c9f061..4330764da 100644 --- a/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs +++ b/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs @@ -48,7 +48,7 @@ public FakeConfigurableIssueProvider( /// /// Gets the repository settings. /// - public RepositorySettings RepositorySettings => this.Settings; + public IRepositorySettings RepositorySettings => this.Settings; /// /// Gets the issue provider settings. diff --git a/src/Cake.Issues.Testing/FakeIssueProvider.cs b/src/Cake.Issues.Testing/FakeIssueProvider.cs index 8dcef5169..ae83e6481 100644 --- a/src/Cake.Issues.Testing/FakeIssueProvider.cs +++ b/src/Cake.Issues.Testing/FakeIssueProvider.cs @@ -42,7 +42,7 @@ public FakeIssueProvider(ICakeLog log, IEnumerable issues) /// /// Gets the settings. /// - public new RepositorySettings Settings => base.Settings; + public new IRepositorySettings Settings => base.Settings; /// public override string ProviderName => "Fake Issue Provider"; diff --git a/src/Cake.Issues.Testing/FakeLogFileFormat.cs b/src/Cake.Issues.Testing/FakeLogFileFormat.cs index 8e39bfd73..6d1dbfc90 100644 --- a/src/Cake.Issues.Testing/FakeLogFileFormat.cs +++ b/src/Cake.Issues.Testing/FakeLogFileFormat.cs @@ -42,7 +42,7 @@ public FakeLogFileFormat(ICakeLog log, IEnumerable issues) /// public override IEnumerable ReadIssues( FakeMultiFormatIssueProvider issueProvider, - RepositorySettings repositorySettings, + IRepositorySettings repositorySettings, FakeMultiFormatIssueProviderSettings issueProviderSettings) { return this.issues; diff --git a/src/Cake.Issues.Testing/FakeMultiFormatIssueProvider.cs b/src/Cake.Issues.Testing/FakeMultiFormatIssueProvider.cs index b53d8b55e..3c35c884a 100644 --- a/src/Cake.Issues.Testing/FakeMultiFormatIssueProvider.cs +++ b/src/Cake.Issues.Testing/FakeMultiFormatIssueProvider.cs @@ -26,7 +26,7 @@ public FakeMultiFormatIssueProvider(ICakeLog log, FakeMultiFormatIssueProviderSe /// /// Gets the repository settings. /// - public RepositorySettings RepositorySettings => this.Settings; + public IRepositorySettings RepositorySettings => this.Settings; /// /// Gets the issue provider settings. diff --git a/src/Cake.Issues.Testing/IssueChecker.cs b/src/Cake.Issues.Testing/IssueChecker.cs index d68a51799..49a4b26a6 100644 --- a/src/Cake.Issues.Testing/IssueChecker.cs +++ b/src/Cake.Issues.Testing/IssueChecker.cs @@ -41,10 +41,16 @@ public static void Check( issueToCheck, expectedIssue.ProviderType, expectedIssue.ProviderName, + expectedIssue.Run, + expectedIssue.Identifier, expectedIssue.ProjectFileRelativePath?.ToString(), expectedIssue.ProjectName, expectedIssue.AffectedFileRelativePath?.ToString(), expectedIssue.Line, + expectedIssue.EndLine, + expectedIssue.Column, + expectedIssue.EndColumn, + expectedIssue.FileLink, expectedIssue.MessageText, expectedIssue.MessageHtml, expectedIssue.MessageMarkdown, @@ -60,6 +66,8 @@ public static void Check( /// Issue which should be checked. /// Expected type of the issue provider. /// Expected human friendly name of the issue provider. + /// Expected name of the run which reported the issue. + /// Expected identifier of the issue. /// Expected relative path of the project file. /// null if the issue is not expected to be related to a project. /// Expected project name. @@ -68,6 +76,14 @@ public static void Check( /// null if the issue is not expected to be related to a change in a file. /// Expected line number. /// null if the issue is not expected to be related to a file or specific line. + /// Expected end of line range. + /// null if the issue is not expected to be related to a file, specific line or range of lines. + /// Expected column. + /// null if the issue is not expected to be related to a file or specific column. + /// Expected end of column range. + /// null if the issue is not expected to be related to a file, specific column or range of columns. + /// Expected file link. + /// null if the issue is not expected to have a file link. /// Expected message in plain text format. /// Expected message in HTML format. /// Expected message in Markdown format. @@ -83,10 +99,16 @@ public static void Check( IIssue issue, string providerType, string providerName, + string run, + string identifier, string projectFileRelativePath, string projectName, string affectedFileRelativePath, int? line, + int? endLine, + int? column, + int? endColumn, + Uri fileLink, string messageText, string messageHtml, string messageMarkdown, @@ -109,6 +131,18 @@ public static void Check( $"Expected issue.ProviderName to be '{providerName}' but was '{issue.ProviderName}'."); } + if (issue.Run != run) + { + throw new Exception( + $"Expected issue.Run to be '{run}' but was '{issue.Run}'."); + } + + if (issue.Identifier != identifier) + { + throw new Exception( + $"Expected issue.Identifier to be '{identifier}' but was '{issue.Identifier}'."); + } + if (issue.ProjectFileRelativePath == null) { if (projectFileRelativePath != null) @@ -122,7 +156,7 @@ public static void Check( if (issue.ProjectFileRelativePath.ToString() != new FilePath(projectFileRelativePath).ToString()) { throw new Exception( - $"Expected issue.ProjectFileRelativePath to be '{projectFileRelativePath}' but was '{issue.ProjectFileRelativePath.ToString()}'."); + $"Expected issue.ProjectFileRelativePath to be '{projectFileRelativePath}' but was '{issue.ProjectFileRelativePath}'."); } if (!issue.ProjectFileRelativePath.IsRelative) @@ -151,7 +185,7 @@ public static void Check( if (issue.AffectedFileRelativePath.ToString() != new FilePath(affectedFileRelativePath).ToString()) { throw new Exception( - $"Expected issue.AffectedFileRelativePath to be '{affectedFileRelativePath}' but was '{issue.AffectedFileRelativePath.ToString()}'."); + $"Expected issue.AffectedFileRelativePath to be '{affectedFileRelativePath}' but was '{issue.AffectedFileRelativePath}'."); } if (!issue.AffectedFileRelativePath.IsRelative) @@ -167,6 +201,30 @@ public static void Check( $"Expected issue.Line to be '{line}' but was '{issue.Line}'."); } + if (issue.EndLine != endLine) + { + throw new Exception( + $"Expected issue.EndLine to be '{endLine}' but was '{issue.EndLine}'."); + } + + if (issue.Column != column) + { + throw new Exception( + $"Expected issue.Column to be '{column}' but was '{issue.Column}'."); + } + + if (issue.EndColumn != endColumn) + { + throw new Exception( + $"Expected issue.EndColumn to be '{endColumn}' but was '{issue.EndColumn}'."); + } + + if (issue.FileLink?.ToString() != fileLink?.ToString()) + { + throw new Exception( + $"Expected issue.FileLink to be '{fileLink}' but was '{issue.FileLink}'."); + } + if (issue.MessageText != messageText) { throw new Exception( diff --git a/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs b/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs index f446e83d8..af78a89b0 100644 --- a/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs +++ b/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs @@ -110,7 +110,7 @@ public void Should_Read_Issues_From_Format() "Foo".ToByteArray(), format); var provider = new FakeMultiFormatIssueProvider(log, settings); - provider.Initialize(new RepositorySettings(@"c:\repo")); + provider.Initialize(new ReadIssuesSettings(@"c:\repo")); // When var result = provider.ReadIssues(); diff --git a/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj b/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj index 23d9e2e35..a21b89da7 100644 --- a/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj +++ b/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj @@ -25,7 +25,7 @@ - + @@ -34,7 +34,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -43,9 +43,15 @@ + + Always + Always + + Always + Always diff --git a/src/Cake.Issues.Tests/FileLinkSettingsTests.cs b/src/Cake.Issues.Tests/FileLinkSettingsTests.cs new file mode 100644 index 000000000..a80847747 --- /dev/null +++ b/src/Cake.Issues.Tests/FileLinkSettingsTests.cs @@ -0,0 +1,133 @@ +namespace Cake.Issues.Tests +{ + using System; + using System.Collections.Generic; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class FileLinkSettingsTests + { + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given + Func, Uri> builder = null; + + // When + var result = Record.Exception(() => new FileLinkSettings(builder)); + + // Then + result.IsArgumentNullException("builder"); + } + } + + public sealed class TheForPatternMethod + { + [Fact] + public void Should_Throw_If_Pattern_Is_Null() + { + // Given + string pattern = null; + + // When + var result = Record.Exception(() => FileLinkSettings.ForPattern(pattern)); + + // Then + result.IsArgumentNullException("pattern"); + } + + [Fact] + public void Should_Throw_If_Pattern_Is_Empty() + { + // Given + var pattern = string.Empty; + + // When + var result = Record.Exception(() => FileLinkSettings.ForPattern(pattern)); + + // Then + result.IsArgumentOutOfRangeException("pattern"); + } + + [Fact] + public void Should_Throw_If_Pattern_Is_WhiteSpace() + { + // Given + var pattern = " "; + + // When + var result = Record.Exception(() => FileLinkSettings.ForPattern(pattern)); + + // Then + result.IsArgumentOutOfRangeException("pattern"); + } + } + + public sealed class TheForActionMethod + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given + Func builder = null; + + // When + var result = Record.Exception(() => FileLinkSettings.ForAction(builder)); + + // Then + result.IsArgumentNullException("builder"); + } + } + + public sealed class TheForGitHubMethod + { + [Fact] + public void Should_Throw_If_RepositoryUrl_Is_Null() + { + // Given + Uri repositoryUrl = null; + + // When + var result = Record.Exception(() => FileLinkSettings.ForGitHub(repositoryUrl)); + + // Then + result.IsArgumentNullException("repositoryUrl"); + } + } + + public sealed class TheForAzureDevOpsMethod + { + [Fact] + public void Should_Throw_If_RepositoryUrl_Is_Null() + { + // Given + Uri repositoryUrl = null; + + // When + var result = Record.Exception(() => FileLinkSettings.ForAzureDevOps(repositoryUrl)); + + // Then + result.IsArgumentNullException("repositoryUrl"); + } + } + + public sealed class TheGetFileLinkMethod + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IIssue issue = null; + + // When + var result = Record.Exception(() => FileLinkSettings.ForPattern("foo").GetFileLink(issue)); + + // Then + result.IsArgumentNullException("issue"); + } + } + } +} diff --git a/src/Cake.Issues.Tests/FileLinking/AzureDevOpsFileLinkSettingsBuilderTests.cs b/src/Cake.Issues.Tests/FileLinking/AzureDevOpsFileLinkSettingsBuilderTests.cs new file mode 100644 index 000000000..cb9f18c89 --- /dev/null +++ b/src/Cake.Issues.Tests/FileLinking/AzureDevOpsFileLinkSettingsBuilderTests.cs @@ -0,0 +1,411 @@ +namespace Cake.Issues.Tests.FileLinking +{ + using System; + using Cake.Issues.FileLinking; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class AzureDevOpsFileLinkSettingsBuilderTests + { + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_RepositoryUrl_Is_Null() + { + // Given + Uri repositoryUrl = null; + + // When + var result = Record.Exception(() => new AzureDevOpsFileLinkSettingsBuilder(repositoryUrl)); + + // Then + result.IsArgumentNullException("repositoryUrl"); + } + } + + public sealed class TheBranchMethod + { + [Fact] + public void Should_Throw_If_BranchName_Is_Null() + { + // Given + string branch = null; + + // When + var result = + Record.Exception(() => new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")).Branch(branch)); + + // Then + result.IsArgumentNullException("branchName"); + } + + [Fact] + public void Should_Throw_If_BranchName_Is_Empty() + { + // Given + var branch = string.Empty; + + // When + var result = + Record.Exception(() => new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")).Branch(branch)); + + // Then + result.IsArgumentOutOfRangeException("branchName"); + } + + [Fact] + public void Should_Throw_If_BranchName_Is_WhiteSpace() + { + // Given + var branch = " "; + + // When + var result = + Record.Exception(() => new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")).Branch(branch)); + + // Then + result.IsArgumentOutOfRangeException("branchName"); + } + + [Theory] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + 30, + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=30")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo/", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + 30, + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=30")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + null, + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=2147483647")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + null, + null, + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster&line=10&lineEnd=12&lineStartColumn=1&lineEndColumn=2147483647")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + null, + null, + null, + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster&line=10&lineEnd=10&lineStartColumn=1&lineEndColumn=2147483647")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + null, + null, + null, + null, + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster")] + [InlineData( + "http://myserver:8080/tfs/defaultcollection/myproject/_git/myrepository", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + 30, + "master", + "http://myserver:8080/tfs/defaultcollection/myproject/_git/myrepository?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=30")] + public void Should_Return_The_Correct_Link( + string repositoryUrl, + string filePath, + int? line, + int? endLine, + int? column, + int? endColumn, + string branch, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath, line, endLine, column, endColumn) + .Create(); + + // When + var result = + new AzureDevOpsFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Branch(branch) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + + [Theory] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "foo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "/foo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "foo/", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "foo/bar", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/bar/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + null, + @"src\ClassLibrary1\ClassLibrary1.csproj", + "master", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GBmaster")] + public void Should_Return_The_Correct_Link_For_RootPath( + string repositoryUrl, + string rootPath, + string filePath, + string branch, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath) + .Create(); + + // When + var result = + new AzureDevOpsFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Branch(branch) + .WithRootPath(rootPath) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + } + + public sealed class TheCommitMethod + { + [Fact] + public void Should_Throw_If_CommitId_Is_Null() + { + // Given + string commitId = null; + + // When + var result = + Record.Exception(() => new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")).Commit(commitId)); + + // Then + result.IsArgumentNullException("commitId"); + } + + [Fact] + public void Should_Throw_If_CommitId_Is_Empty() + { + // Given + var commitId = string.Empty; + + // When + var result = + Record.Exception(() => new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")).Commit(commitId)); + + // Then + result.IsArgumentOutOfRangeException("commitId"); + } + + [Fact] + public void Should_Throw_If_CommitId_Is_WhiteSpace() + { + // Given + var commitId = " "; + + // When + var result = + Record.Exception(() => new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")).Commit(commitId)); + + // Then + result.IsArgumentOutOfRangeException("commitId"); + } + + [Theory] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + 30, + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=30")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo/", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + 30, + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=30")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + null, + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=2147483647")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + null, + null, + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466&line=10&lineEnd=12&lineStartColumn=1&lineEndColumn=2147483647")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + null, + null, + null, + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466&line=10&lineEnd=10&lineStartColumn=1&lineEndColumn=2147483647")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + null, + null, + null, + null, + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466")] + [InlineData( + "http://myserver:8080/tfs/defaultcollection/myproject/_git/myrepository", + @"src\ClassLibrary1\ClassLibrary1.csproj", + 10, + 12, + 20, + 30, + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "http://myserver:8080/tfs/defaultcollection/myproject/_git/myrepository?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466&line=10&lineEnd=12&lineStartColumn=20&lineEndColumn=30")] + public void Should_Return_The_Correct_Link( + string repositoryUrl, + string filePath, + int? line, + int? endLine, + int? column, + int? endColumn, + string commitId, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath, line, endLine, column, endColumn) + .Create(); + + // When + var result = + new AzureDevOpsFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Commit(commitId) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + + [Theory] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "foo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "/foo", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "foo/", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + "foo/bar", + @"src\ClassLibrary1\ClassLibrary1.csproj", + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/foo/bar/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466")] + [InlineData( + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo", + null, + @"src\ClassLibrary1\ClassLibrary1.csproj", + "734bd70b03e45741426ed2916d1fa72c6ff20466", + "https://dev.azure.com/pberger/_git/Cake.Issues-Demo?path=/src/ClassLibrary1/ClassLibrary1.csproj&version=GC734bd70b03e45741426ed2916d1fa72c6ff20466")] + public void Should_Return_The_Correct_Link_For_RootPath( + string repositoryUrl, + string rootPath, + string filePath, + string commitId, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath) + .Create(); + + // When + var result = + new AzureDevOpsFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Commit(commitId) + .WithRootPath(rootPath) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + } + } +} diff --git a/src/Cake.Issues.Tests/FileLinking/FileLinkOptionalSettingsBuilderTests.cs b/src/Cake.Issues.Tests/FileLinking/FileLinkOptionalSettingsBuilderTests.cs new file mode 100644 index 000000000..75f48322e --- /dev/null +++ b/src/Cake.Issues.Tests/FileLinking/FileLinkOptionalSettingsBuilderTests.cs @@ -0,0 +1,98 @@ +namespace Cake.Issues.Tests.FileLinking +{ + using System; + using System.Collections.Generic; + using Cake.Issues.FileLinking; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class FileLinkOptionalSettingsBuilderTests + { + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given + Func, Uri> builder = null; + + // When + var result = Record.Exception(() => new FileLinkOptionalSettingsBuilder(builder)); + + // Then + result.IsArgumentNullException("builder"); + } + } + + public sealed class TheWithRootPathMethod + { + [Fact] + public void Should_Not_Throw_If_RootPath_Is_Null() + { + // Given + string rootPath = null; + + // When + var result = + new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")) + .Branch("master") + .WithRootPath(rootPath); + + // Then + result.ShouldNotBeNull(); + } + + [Fact] + public void Should_Throw_If_RootPath_Is_Empty() + { + // Given + var rootPath = string.Empty; + + // When + var result = + Record.Exception(() => + new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")) + .Branch("master") + .WithRootPath(rootPath)); + + // Then + result.IsArgumentOutOfRangeException("rootPath"); + } + + [Fact] + public void Should_Throw_If_RootPath_Is_WhiteSpace() + { + // Given + var rootPath = " "; + + // When + var result = + Record.Exception(() => + new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")) + .Branch("master") + .WithRootPath(rootPath)); + + // Then + result.IsArgumentOutOfRangeException("rootPath"); + } + + [Theory] + [InlineData("foo\tbar")] + public void Should_Throw_If_RootPath_Is_Invalid(string rootPath) + { + // Given + + // When + var result = + Record.Exception(() => + new AzureDevOpsFileLinkSettingsBuilder(new Uri("https://github.com")) + .Branch("master") + .WithRootPath(rootPath)); + + // Then + result.IsArgumentException("rootPath"); + } + } + } +} diff --git a/src/Cake.Issues.Tests/FileLinking/GitHubFileLinkSettingsBuilderTests.cs b/src/Cake.Issues.Tests/FileLinking/GitHubFileLinkSettingsBuilderTests.cs new file mode 100644 index 000000000..7db305c89 --- /dev/null +++ b/src/Cake.Issues.Tests/FileLinking/GitHubFileLinkSettingsBuilderTests.cs @@ -0,0 +1,339 @@ +namespace Cake.Issues.Tests.FileLinking +{ + using System; + using Cake.Issues.FileLinking; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class GitHubFileLinkSettingsBuilderTests + { + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_RepositoryUrl_Is_Null() + { + // Given + Uri repositoryUrl = null; + + // When + var result = Record.Exception(() => new GitHubFileLinkSettingsBuilder(repositoryUrl)); + + // Then + result.IsArgumentNullException("repositoryUrl"); + } + } + + public sealed class TheBranchMethod + { + [Fact] + public void Should_Throw_If_BranchName_Is_Null() + { + // Given + string branch = null; + + // When + var result = + Record.Exception(() => new GitHubFileLinkSettingsBuilder(new Uri("https://github.com")).Branch(branch)); + + // Then + result.IsArgumentNullException("branchName"); + } + + [Fact] + public void Should_Throw_If_BranchName_Is_Empty() + { + // Given + var branch = string.Empty; + + // When + var result = + Record.Exception(() => new GitHubFileLinkSettingsBuilder(new Uri("https://github.com")).Branch(branch)); + + // Then + result.IsArgumentOutOfRangeException("branchName"); + } + + [Fact] + public void Should_Throw_If_BranchName_Is_WhiteSpace() + { + // Given + var branch = " "; + + // When + var result = + Record.Exception(() => new GitHubFileLinkSettingsBuilder(new Uri("https://github.com")).Branch(branch)); + + // Then + result.IsArgumentOutOfRangeException("branchName"); + } + + [Theory] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues", + @"src\Cake.Issues\Cake.Issues.csproj", + 10, + 12, + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/src/Cake.Issues/Cake.Issues.csproj#L10-L12")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + @"src\Cake.Issues\Cake.Issues.csproj", + 10, + 12, + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/src/Cake.Issues/Cake.Issues.csproj#L10-L12")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + @"src\Cake.Issues\Cake.Issues.csproj", + 10, + null, + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/src/Cake.Issues/Cake.Issues.csproj#L10")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + @"src\Cake.Issues\Cake.Issues.csproj", + null, + null, + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/src/Cake.Issues/Cake.Issues.csproj")] + public void Should_Return_The_Correct_Link( + string repositoryUrl, + string filePath, + int? line, + int? endLine, + string branch, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath, line, endLine, null, null) + .Create(); + + // When + var result = + new GitHubFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Branch(branch) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + + [Theory] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "foo", + @"src\Cake.Issues\Cake.Issues.csproj", + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/foo/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "/foo", + @"src\Cake.Issues\Cake.Issues.csproj", + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/foo/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "foo/", + @"src\Cake.Issues\Cake.Issues.csproj", + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/foo/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "foo/bar", + @"src\Cake.Issues\Cake.Issues.csproj", + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/foo/bar/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + null, + @"src\Cake.Issues\Cake.Issues.csproj", + "master", + "https://github.com/cake-contrib/Cake.Issues/blob/master/src/Cake.Issues/Cake.Issues.csproj")] + public void Should_Return_The_Correct_Link_For_RootPath( + string repositoryUrl, + string rootPath, + string filePath, + string branch, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath) + .Create(); + + // When + var result = + new GitHubFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Branch(branch) + .WithRootPath(rootPath) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + } + + public sealed class TheCommitMethod + { + [Fact] + public void Should_Throw_If_CommitId_Is_Null() + { + // Given + string commitId = null; + + // When + var result = + Record.Exception(() => new GitHubFileLinkSettingsBuilder(new Uri("https://github.com")).Commit(commitId)); + + // Then + result.IsArgumentNullException("commitId"); + } + + [Fact] + public void Should_Throw_If_CommitId_Is_Empty() + { + // Given + var commitId = string.Empty; + + // When + var result = + Record.Exception(() => new GitHubFileLinkSettingsBuilder(new Uri("https://github.com")).Commit(commitId)); + + // Then + result.IsArgumentOutOfRangeException("commitId"); + } + + [Fact] + public void Should_Throw_If_CommitId_Is_WhiteSpace() + { + // Given + var commitId = " "; + + // When + var result = + Record.Exception(() => new GitHubFileLinkSettingsBuilder(new Uri("https://github.com")).Commit(commitId)); + + // Then + result.IsArgumentOutOfRangeException("commitId"); + } + + [Theory] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues", + @"src\Cake.Issues\Cake.Issues.csproj", + 10, + 12, + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/src/Cake.Issues/Cake.Issues.csproj#L10-L12")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + @"src\Cake.Issues\Cake.Issues.csproj", + 10, + 12, + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/src/Cake.Issues/Cake.Issues.csproj#L10-L12")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + @"src\Cake.Issues\Cake.Issues.csproj", + 10, + null, + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/src/Cake.Issues/Cake.Issues.csproj#L10")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + @"src\Cake.Issues\Cake.Issues.csproj", + null, + null, + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/src/Cake.Issues/Cake.Issues.csproj")] + public void Should_Return_The_Correct_Link( + string repositoryUrl, + string filePath, + int? line, + int? endLine, + string commitId, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath, line, endLine, null, null) + .OfRule("Foo") + .WithPriority(IssuePriority.Warning) + .Create(); + + // When + var result = + new GitHubFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Commit(commitId) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + + [Theory] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "foo", + @"src\Cake.Issues\Cake.Issues.csproj", + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/foo/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "/foo", + @"src\Cake.Issues\Cake.Issues.csproj", + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/foo/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "foo/", + @"src\Cake.Issues\Cake.Issues.csproj", + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/foo/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + "foo/bar", + @"src\Cake.Issues\Cake.Issues.csproj", + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/foo/bar/src/Cake.Issues/Cake.Issues.csproj")] + [InlineData( + "https://github.com/cake-contrib/Cake.Issues/", + null, + @"src\Cake.Issues\Cake.Issues.csproj", + "6d035013812a4a3ab8be5e84c0f2a8b5ce60720a", + "https://github.com/cake-contrib/Cake.Issues/blob/6d035013812a4a3ab8be5e84c0f2a8b5ce60720a/src/Cake.Issues/Cake.Issues.csproj")] + public void Should_Return_The_Correct_Link_For_RootPath( + string repositoryUrl, + string rootPath, + string filePath, + string commitId, + string expectedLink) + { + // Given + var issue = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath) + .Create(); + + // When + var result = + new GitHubFileLinkSettingsBuilder(new Uri(repositoryUrl)) + .Commit(commitId) + .WithRootPath(rootPath) + .GetFileLink(issue); + + // Then + result.ToString().ShouldBe(expectedLink); + } + } + } +} diff --git a/src/Cake.Issues.Tests/FileLinking/IDictionaryExtensionsTests.cs b/src/Cake.Issues.Tests/FileLinking/IDictionaryExtensionsTests.cs new file mode 100644 index 000000000..430b7705d --- /dev/null +++ b/src/Cake.Issues.Tests/FileLinking/IDictionaryExtensionsTests.cs @@ -0,0 +1,55 @@ +namespace Cake.Issues.Tests.FileLinking +{ + using Cake.Issues.FileLinking; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class IDictionaryExtensionsTests + { + public sealed class TheGetValueOrDefaultExtension + { + [Fact] + public void Should_Throw_If_Dictionary_Is_Null() + { + // Given + System.Collections.Generic.IDictionary dictionary = null; + + // When + var result = Record.Exception(() => dictionary.GetValueOrDefault("foo", null)); + + // Then + result.IsArgumentNullException("dictionary"); + } + + [Fact] + public void Should_Return_Value_If_Exists() + { + // Given + var key = "foo"; + var value = "bar"; + var dictionary = new System.Collections.Generic.Dictionary { { key, value } }; + + // When + var result = dictionary.GetValueOrDefault(key, null); + + // Then + result.ShouldBe(value); + } + + [Fact] + public void Should_Return_DefaultValue_If_Value_Does_Not_Exist() + { + // Given + var defaultValue = "defaultValue"; + var dictionary = new System.Collections.Generic.Dictionary { { "foo", "bar" } }; + + // When + var result = dictionary.GetValueOrDefault("bar", defaultValue); + + // Then + result.ShouldBe(defaultValue); + } + } + } +} diff --git a/src/Cake.Issues.Tests/IIssueComparerTests.cs b/src/Cake.Issues.Tests/IIssueComparerTests.cs index 169003a5a..ab0f7ed2c 100644 --- a/src/Cake.Issues.Tests/IIssueComparerTests.cs +++ b/src/Cake.Issues.Tests/IIssueComparerTests.cs @@ -131,6 +131,65 @@ public void Should_Return_False_If_Line_Is_Different(int? line1, int? line2) CompareIssues(issue1, issue2, false); } + [Theory] + [InlineData(1, 2)] + [InlineData(1, null)] + [InlineData(null, 1)] + [InlineData(int.MaxValue, 1)] + [InlineData(1, int.MaxValue)] + public void Should_Return_False_If_Column_Is_Different(int? column1, int? column2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("http://foo", "http://bar")] + [InlineData("http://foo", null)] + [InlineData(null, "http://foo")] + public void Should_Return_False_If_FileLink_Is_Different(string fileLink1, string fileLink2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink1)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink2)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + [Fact] public void Should_Return_False_If_MessageText_Is_Different() { @@ -268,7 +327,7 @@ public void Should_Return_False_If_Rule_Is_Different(string rule1, string rule2) [InlineData("http://foo", "http://bar")] [InlineData("http://foo", null)] [InlineData(null, "http://foo")] - public void Should_Return_False_If_RuleUlr_Is_Different(string ruleUrl1, string ruleUrl2) + public void Should_Return_False_If_RuleUrl_Is_Different(string ruleUrl1, string ruleUrl2) { // Given var issueBuilder = @@ -333,6 +392,25 @@ public void Should_Return_False_If_ProviderName_Is_Different() CompareIssues(issue1, issue2, false); } + [Fact] + public void Should_Return_False_If_Run_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run1") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run2") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + [Fact] public void Should_Return_True_If_Same_Reference() { @@ -467,6 +545,63 @@ public void Should_Return_True_If_Line_Is_Same(int? line1, int? line2) CompareIssues(issue1, issue2, true); } + [Theory] + [InlineData(1, 1)] + [InlineData(null, null)] + [InlineData(int.MaxValue, int.MaxValue)] + public void Should_Return_True_If_Column_Is_Same(int? column1, int? column2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("http://foo", "http://foo")] + [InlineData("http://foo", "http://Foo")] + [InlineData(null, null)] + public void Should_Return_True_If_FileLink_Is_Same(string fileLink1, string fileLink2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink1)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink2)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + [Fact] public void Should_Return_True_If_MessageText_Is_Same() { @@ -600,7 +735,7 @@ public void Should_Return_True_If_Rule_Is_Same(string rule1, string rule2) [InlineData("http://foo", "http://foo")] [InlineData("http://foo", "http://Foo")] [InlineData(null, null)] - public void Should_Return_True_If_RuleUlr_Is_Same(string ruleUrl1, string ruleUrl2) + public void Should_Return_True_If_RuleUrl_Is_Same(string ruleUrl1, string ruleUrl2) { // Given var issueBuilder = @@ -665,6 +800,25 @@ public void Should_Return_True_If_ProviderName_Is_Same() CompareIssues(issue1, issue2, true); } + [Fact] + public void Should_Return_True_If_Run_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + [Fact] public void Should_Remove_Identical_Issues_From_List_Of_Issues() { @@ -904,7 +1058,7 @@ public void Should_Return_False_If_Rule_Is_Different(string rule1, string rule2) [InlineData("http://foo", "http://bar")] [InlineData("http://foo", null)] [InlineData(null, "http://foo")] - public void Should_Return_False_If_RuleUlr_Is_Different(string ruleUrl1, string ruleUrl2) + public void Should_Return_False_If_RuleUrl_Is_Different(string ruleUrl1, string ruleUrl2) { // Given var issueBuilder = @@ -969,6 +1123,25 @@ public void Should_Return_False_If_ProviderName_Is_Different() CompareIssues(issue1, issue2, false); } + [Fact] + public void Should_Return_False_If_Run_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run1") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run2") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + [Fact] public void Should_Return_True_If_Same_Reference() { @@ -1173,6 +1346,122 @@ public void Should_Return_True_If_Line_Is_Same(int? line1, int? line2) CompareIssues(issue1, issue2, true); } + [Theory] + [InlineData(1, 2)] + [InlineData(1, null)] + [InlineData(null, 1)] + [InlineData(int.MaxValue, 1)] + [InlineData(1, int.MaxValue)] + public void Should_Return_True_If_Column_Is_Different(int? column1, int? column2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(null, null)] + [InlineData(int.MaxValue, int.MaxValue)] + public void Should_Return_True_If_Column_Is_Same(int? column1, int? column2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", 42, column2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("http://foo", "http://bar")] + [InlineData("http://foo", null)] + [InlineData(null, "http://foo")] + public void Should_Return_True_If_FileLink_Is_Different(string fileLink1, string fileLink2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink1)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink2)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("http://foo", "http://foo")] + [InlineData("http://foo", "http://Foo")] + [InlineData(null, null)] + public void Should_Return_True_If_FileLink_Is_Same(string fileLink1, string fileLink2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink1)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(fileLink2)) + { + issueBuilder = + issueBuilder + .WithFileLink(new Uri(fileLink2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + [Fact] public void Should_Return_True_If_MessageText_Is_Same() { @@ -1306,7 +1595,7 @@ public void Should_Return_True_If_Rule_Is_Same(string rule1, string rule2) [InlineData("http://foo", "http://foo")] [InlineData("http://foo", "http://Foo")] [InlineData(null, null)] - public void Should_Return_True_If_RuleUlr_Is_Same(string ruleUrl1, string ruleUrl2) + public void Should_Return_True_If_RuleUrl_Is_Same(string ruleUrl1, string ruleUrl2) { // Given var issueBuilder = @@ -1371,6 +1660,25 @@ public void Should_Return_True_If_ProviderName_Is_Same() CompareIssues(issue1, issue2, true); } + [Fact] + public void Should_Return_True_If_Run_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun("run") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + [Fact] public void Should_Remove_Identical_Issues_From_List_Of_Issues() { diff --git a/src/Cake.Issues.Tests/IIssueExtensionsTests.cs b/src/Cake.Issues.Tests/IIssueExtensionsTests.cs index c444ac375..ca488dbef 100644 --- a/src/Cake.Issues.Tests/IIssueExtensionsTests.cs +++ b/src/Cake.Issues.Tests/IIssueExtensionsTests.cs @@ -7,6 +7,98 @@ public sealed class IIssueExtensionsTests { + public sealed class TheLineRangeExtension + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IIssue issue = null; + + // When + var result = Record.Exception(() => issue.LineRange()); + + // Then + result.IsArgumentNullException("issue"); + } + + [Theory] + [InlineData(null, null, null, null, "")] + [InlineData(10, null, null, null, "10")] + [InlineData(23, 42, null, null, "23-42")] + [InlineData(23, 42, 5, null, "23:5-42")] + [InlineData(23, 42, 5, 10, "23:5-42:10")] + public void Should_Return_Correct_LineRange( + int? startLine, + int? endLine, + int? startColumn, + int? endColumn, + string expectedLineRange) + { + // Given + var issue = + IssueBuilder + .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo") + .InFile("foo.ch", startLine, endLine, startColumn, endColumn) + .Create(); + + // When + var result = issue.LineRange(); + + // Then + result.ShouldBe(expectedLineRange); + } + } + + public sealed class TheLineRangeExtensionWithAddColumnParameter + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IIssue issue = null; + + // When + var result = Record.Exception(() => issue.LineRange(false)); + + // Then + result.IsArgumentNullException("issue"); + } + + [Theory] + [InlineData(null, null, null, null, true, "")] + [InlineData(10, null, null, null, true, "10")] + [InlineData(23, 42, null, null, true, "23-42")] + [InlineData(23, 42, 5, null, true, "23:5-42")] + [InlineData(23, 42, 5, 10, true, "23:5-42:10")] + [InlineData(null, null, null, null, false, "")] + [InlineData(10, null, null, null, false, "10")] + [InlineData(23, 42, null, null, false, "23-42")] + [InlineData(23, 42, 5, null, false, "23-42")] + [InlineData(23, 42, 5, 10, false, "23-42")] + public void Should_Return_Correct_LineRange( + int? startLine, + int? endLine, + int? startColumn, + int? endColumn, + bool addColumnInformation, + string expectedLineRange) + { + // Given + var issue = + IssueBuilder + .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo") + .InFile("foo.ch", startLine, endLine, startColumn, endColumn) + .Create(); + + // When + var result = issue.LineRange(addColumnInformation); + + // Then + result.ShouldBe(expectedLineRange); + } + } + public sealed class TheProjectPathExtension { [Fact] @@ -297,6 +389,8 @@ public void Should_Throw_If_Issue_Is_Null() [InlineData("{foo}", "{foo}")] [InlineData("foo {ProviderType} bar", "foo ProviderType Foo bar")] [InlineData("foo {ProviderName} bar", "foo ProviderName Foo bar")] + [InlineData("foo {Run} bar", "foo Run bar")] + [InlineData("foo {Identifier} bar", "foo Identifier Foo bar")] [InlineData("foo {Priority} bar", "foo 400 bar")] [InlineData("foo {PriorityName} bar", "foo Error bar")] [InlineData("foo {ProjectPath} bar", "foo src/Cake.Issues/Cake.Issues.csproj bar")] @@ -306,6 +400,10 @@ public void Should_Throw_If_Issue_Is_Null() [InlineData("foo {FileDirectory} bar", "foo src/Cake.Issues bar")] [InlineData("foo {FileName} bar", "foo foo.cs bar")] [InlineData("foo {Line} bar", "foo 42 bar")] + [InlineData("foo {EndLine} bar", "foo 420 bar")] + [InlineData("foo {Column} bar", "foo 23 bar")] + [InlineData("foo {EndColumn} bar", "foo 230 bar")] + [InlineData("foo {FileLink} bar", "foo https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12 bar")] [InlineData("foo {Rule} bar", "foo Rule Foo bar")] [InlineData("foo {RuleUrl} bar", "foo https://google.com/ bar")] [InlineData("foo {MessageText} bar", "foo MessageText Foo bar")] @@ -316,11 +414,13 @@ public void Should_Replace_Tokens(string pattern, string expectedResult) // Given var issue = IssueBuilder - .NewIssue("MessageText Foo", "ProviderType Foo", "ProviderName Foo") + .NewIssue("Identifier Foo", "MessageText Foo", "ProviderType Foo", "ProviderName Foo") + .ForRun("Run") .WithMessageInHtmlFormat("MessageHtml Foo") .WithMessageInMarkdownFormat("MessageMarkdown Foo") - .InFile(@"src/Cake.Issues/foo.cs", 42) + .InFile(@"src/Cake.Issues/foo.cs", 42, 420, 23, 230) .InProject(@"src/Cake.Issues/Cake.Issues.csproj", "Cake.Issues") + .WithFileLink(new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12")) .OfRule("Rule Foo", new Uri("https://google.com")) .WithPriority(IssuePriority.Error) .Create(); diff --git a/src/Cake.Issues.Tests/IssueBuilderFixture.cs b/src/Cake.Issues.Tests/IssueBuilderFixture.cs index 7b5e1cbc0..c81ff3cf1 100644 --- a/src/Cake.Issues.Tests/IssueBuilderFixture.cs +++ b/src/Cake.Issues.Tests/IssueBuilderFixture.cs @@ -3,14 +3,14 @@ internal class IssueBuilderFixture { public IssueBuilderFixture() - : this("Message", "ProviderType", "ProviderName") + : this("Identifier", "Message", "ProviderType", "ProviderName") { } - public IssueBuilderFixture(string messageText, string providerType, string providerName) + public IssueBuilderFixture(string identifier, string messageText, string providerType, string providerName) { this.IssueBuilder = - IssueBuilder.NewIssue(messageText, providerType, providerName); + IssueBuilder.NewIssue(identifier, messageText, providerType, providerName); } public IssueBuilder IssueBuilder { get; private set; } diff --git a/src/Cake.Issues.Tests/IssueBuilderTests.cs b/src/Cake.Issues.Tests/IssueBuilderTests.cs index d73fb1bf5..8ac9eec94 100644 --- a/src/Cake.Issues.Tests/IssueBuilderTests.cs +++ b/src/Cake.Issues.Tests/IssueBuilderTests.cs @@ -8,7 +8,7 @@ public sealed class IssueBuilderTests { - public sealed class TheNewIssueMethod + public sealed class TheNewIssueMethodWithMessageAsIdentifier { [Fact] public void Should_Throw_If_Message_Is_Null() @@ -153,20 +153,145 @@ public void Should_Throw_If_ProviderName_Is_WhiteSpace() // Then result.IsArgumentOutOfRangeException("providerName"); } + + [Fact] + public void Should_Set_Identifier() + { + // Given + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(message, providerType, providerName) + .Create(); + + // Then + result.Identifier.ShouldBe(message); + } + + [Fact] + public void Should_Set_Message() + { + // Given + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(message, providerType, providerName) + .Create(); + + // Then + result.MessageText.ShouldBe(message); + } + + [Fact] + public void Should_Set_ProviderType() + { + // Given + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(message, providerType, providerName) + .Create(); + + // Then + result.ProviderType.ShouldBe(providerType); + } + + [Fact] + public void Should_Set_ProviderName() + { + // Given + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(message, providerType, providerName) + .Create(); + + // Then + result.ProviderName.ShouldBe(providerName); + } } - public sealed class TheNewIssueOfTMethod + public sealed class TheNewIssueMethod { + [Fact] + public void Should_Throw_If_Identifier_Is_Null() + { + // Given + string identifier = null; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentNullException("identifier"); + } + + [Fact] + public void Should_Throw_If_Identifier_Is_Empty() + { + // Given + var identifier = string.Empty; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentOutOfRangeException("identifier"); + } + + [Fact] + public void Should_Throw_If_Identifier_Is_WhiteSpace() + { + // Given + var identifier = " "; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentOutOfRangeException("identifier"); + } + [Fact] public void Should_Throw_If_Message_Is_Null() { // Given + var identifier = "Identifier"; string message = null; - var issueProvider = new FakeIssueProvider(new FakeLog()); + var providerType = "ProviderType"; + var providerName = "ProviderName"; // When var result = Record.Exception(() => - IssueBuilder.NewIssue(message, issueProvider)); + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); // Then result.IsArgumentNullException("message"); @@ -176,12 +301,14 @@ public void Should_Throw_If_Message_Is_Null() public void Should_Throw_If_Message_Is_Empty() { // Given + var identifier = "Identifier"; var message = string.Empty; - var issueProvider = new FakeIssueProvider(new FakeLog()); + var providerType = "ProviderType"; + var providerName = "ProviderName"; // When var result = Record.Exception(() => - IssueBuilder.NewIssue(message, issueProvider)); + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); // Then result.IsArgumentOutOfRangeException("message"); @@ -191,313 +318,1206 @@ public void Should_Throw_If_Message_Is_Empty() public void Should_Throw_If_Message_Is_WhiteSpace() { // Given + var identifier = "Identifier"; var message = " "; - var issueProvider = new FakeIssueProvider(new FakeLog()); + var providerType = "ProviderType"; + var providerName = "ProviderName"; // When var result = Record.Exception(() => - IssueBuilder.NewIssue(message, issueProvider)); + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); // Then result.IsArgumentOutOfRangeException("message"); } [Fact] - public void Should_Throw_If_IssueProvider_Is_Null() + public void Should_Throw_If_ProviderType_Is_Null() { // Given + var identifier = "Identifier"; var message = "Message"; - IIssueProvider issueProvider = null; + string providerType = null; + var providerName = "ProviderName"; // When var result = Record.Exception(() => - IssueBuilder.NewIssue(message, issueProvider)); + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); // Then - result.IsArgumentNullException("issueProvider"); + result.IsArgumentNullException("providerType"); + } + + [Fact] + public void Should_Throw_If_ProviderType_Is_Empty() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = string.Empty; + var providerName = "ProviderName"; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentOutOfRangeException("providerType"); + } + + [Fact] + public void Should_Throw_If_ProviderType_Is_WhiteSpace() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = " "; + var providerName = "ProviderName"; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentOutOfRangeException("providerType"); + } + + [Fact] + public void Should_Throw_If_ProviderName_Is_Null() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = "ProviderType"; + string providerName = null; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentNullException("providerName"); + } + + [Fact] + public void Should_Throw_If_ProviderName_Is_Empty() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = string.Empty; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentOutOfRangeException("providerName"); + } + + [Fact] + public void Should_Throw_If_ProviderName_Is_WhiteSpace() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = " "; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, providerType, providerName)); + + // Then + result.IsArgumentOutOfRangeException("providerName"); + } + + [Fact] + public void Should_Set_Identifier() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, providerType, providerName) + .Create(); + + // Then + result.Identifier.ShouldBe(identifier); + } + + [Fact] + public void Should_Set_Message() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, providerType, providerName) + .Create(); + + // Then + result.MessageText.ShouldBe(message); + } + + [Fact] + public void Should_Set_ProviderType() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, providerType, providerName) + .Create(); + + // Then + result.ProviderType.ShouldBe(providerType); + } + + [Fact] + public void Should_Set_ProviderName() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, providerType, providerName) + .Create(); + + // Then + result.ProviderName.ShouldBe(providerName); } } - public sealed class TheInProjectFileMethod + public sealed class TheNewIssueOfTMethodWithMessageAsIdentifier { [Fact] - public void Should_Handle_Project_Paths_Which_Are_Null() + public void Should_Throw_If_Message_Is_Null() { // Given - var fixture = new IssueBuilderFixture(); - string projectPath = null; + string message = null; + var issueProvider = new FakeIssueProvider(new FakeLog()); // When - var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + var result = Record.Exception(() => + IssueBuilder.NewIssue(message, issueProvider)); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + result.IsArgumentNullException("message"); } [Fact] - public void Should_Handle_Project_Paths_Which_Are_Empty() + public void Should_Throw_If_Message_Is_Empty() + { + // Given + var message = string.Empty; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(message, issueProvider)); + + // Then + result.IsArgumentOutOfRangeException("message"); + } + + [Fact] + public void Should_Throw_If_Message_Is_WhiteSpace() + { + // Given + var message = " "; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(message, issueProvider)); + + // Then + result.IsArgumentOutOfRangeException("message"); + } + + [Fact] + public void Should_Throw_If_IssueProvider_Is_Null() + { + // Given + var message = "Message"; + IIssueProvider issueProvider = null; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(message, issueProvider)); + + // Then + result.IsArgumentNullException("issueProvider"); + } + + [Fact] + public void Should_Set_Identifier() + { + // Given + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(message, issueProvider) + .Create(); + + // Then + result.Identifier.ShouldBe(message); + } + + [Fact] + public void Should_Set_Message() + { + // Given + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(message, issueProvider) + .Create(); + + // Then + result.MessageText.ShouldBe(message); + } + + [Fact] + public void Should_Set_ProviderType() + { + // Given + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(message, issueProvider) + .Create(); + + // Then + result.ProviderType.ShouldBe(issueProvider.GetType().FullName); + } + + [Fact] + public void Should_Set_ProviderName() + { + // Given + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(message, issueProvider) + .Create(); + + // Then + result.ProviderName.ShouldBe(issueProvider.ProviderName); + } + } + + public sealed class TheNewIssueOfTMethod + { + [Fact] + public void Should_Throw_If_Identifier_Is_Null() + { + // Given + string identifier = null; + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, issueProvider)); + + // Then + result.IsArgumentNullException("identifier"); + } + + [Fact] + public void Should_Throw_If_Identifier_Is_Empty() + { + // Given + var identifier = string.Empty; + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, issueProvider)); + + // Then + result.IsArgumentOutOfRangeException("identifier"); + } + + [Fact] + public void Should_Throw_If_Identifier_Is_WhiteSpace() + { + // Given + var identifier = " "; + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, issueProvider)); + + // Then + result.IsArgumentOutOfRangeException("identifier"); + } + + [Fact] + public void Should_Throw_If_Message_Is_Null() + { + // Given + var identifier = "Identifier"; + string message = null; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, issueProvider)); + + // Then + result.IsArgumentNullException("message"); + } + + [Fact] + public void Should_Throw_If_Message_Is_Empty() + { + // Given + var identifier = "Identifier"; + var message = string.Empty; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, issueProvider)); + + // Then + result.IsArgumentOutOfRangeException("message"); + } + + [Fact] + public void Should_Throw_If_Message_Is_WhiteSpace() + { + // Given + var identifier = "Identifier"; + var message = " "; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, issueProvider)); + + // Then + result.IsArgumentOutOfRangeException("message"); + } + + [Fact] + public void Should_Throw_If_IssueProvider_Is_Null() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + IIssueProvider issueProvider = null; + + // When + var result = Record.Exception(() => + IssueBuilder.NewIssue(identifier, message, issueProvider)); + + // Then + result.IsArgumentNullException("issueProvider"); + } + + [Fact] + public void Should_Set_Identifier() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, issueProvider) + .Create(); + + // Then + result.Identifier.ShouldBe(identifier); + } + + [Fact] + public void Should_Set_Message() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, issueProvider) + .Create(); + + // Then + result.MessageText.ShouldBe(message); + } + + [Fact] + public void Should_Set_ProviderType() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, issueProvider) + .Create(); + + // Then + result.ProviderType.ShouldBe(issueProvider.GetType().FullName); + } + + [Fact] + public void Should_Set_ProviderName() + { + // Given + var identifier = "Identifier"; + var message = "Message"; + var issueProvider = new FakeIssueProvider(new FakeLog()); + + // When + var result = + IssueBuilder + .NewIssue(identifier, message, issueProvider) + .Create(); + + // Then + result.ProviderName.ShouldBe(issueProvider.ProviderName); + } + } + + public sealed class TheInProjectFileMethod + { + [Fact] + public void Should_Handle_Project_Paths_Which_Are_Null() + { + // Given + var fixture = new IssueBuilderFixture(); + string projectPath = null; + + // When + var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_Project_Paths_Which_Are_Empty() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectPath = string.Empty; + + // When + var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_Project_Path_Which_Are_WhiteSpace() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectPath = " "; + + // When + var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Theory] + [InlineData(@"src/project.csproj")] + public void Should_Set_ProjectFileRelativePath(string projectPath) + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + + // Then + issue.ProjectFileRelativePath.ToString().ShouldBe(projectPath); + } + } + + public sealed class TheInProjectOfNameMethod + { + [Fact] + public void Should_Handle_Project_Names_Which_Are_Null() + { + // Given + var fixture = new IssueBuilderFixture(); + string projectName = null; + + // When + var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Fact] + public void Should_Handle_Project_Names_Which_Are_Empty() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectName = string.Empty; + + // When + var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Fact] + public void Should_Handle_Project_Names_Which_Are_WhiteSpace() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectName = " "; + + // When + var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Theory] + [InlineData("project")] + public void Should_Set_ProjectName(string projectName) + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + } + + public sealed class TheInProjectMethod + { + [Fact] + public void Should_Handle_Project_Paths_Which_Are_Null() + { + // Given + var fixture = new IssueBuilderFixture(); + string projectPath = null; + var projectName = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_Project_Paths_Which_Are_Empty() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectPath = string.Empty; + var projectName = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectPath = " "; + var projectName = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_Project_Names_Which_Are_Null() + { + // Given + var fixture = new IssueBuilderFixture(); + string projectName = null; + var projectPath = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Fact] + public void Should_Handle_Project_Names_Which_Are_Empty() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectName = string.Empty; + var projectPath = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Fact] + public void Should_Handle_Project_Names_Which_Are_WhiteSpace() + { + // Given + var fixture = new IssueBuilderFixture(); + var projectName = " "; + var projectPath = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Theory] + [InlineData(@"src/project.csproj")] + public void Should_Set_ProjectFileRelativePath(string projectPath) + { + // Given + var fixture = new IssueBuilderFixture(); + var projectName = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectFileRelativePath.ToString().ShouldBe(projectPath); + } + + [Theory] + [InlineData("project")] + public void Should_Set_ProjectName(string projectName) + { + // Given + var fixture = new IssueBuilderFixture(); + var projectPath = "foo"; + + // When + var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + } + + public sealed class TheInFileMethod + { + [Fact] + public void Should_Handle_File_Paths_Which_Are_Null() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InFile(null).Create(); + + // Then + issue.AffectedFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_File_Paths_Which_Are_Empty() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InFile(string.Empty).Create(); + + // Then + issue.AffectedFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_File_Paths_Which_Are_WhiteSpace() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InFile(" ").Create(); + + // Then + issue.AffectedFileRelativePath.ShouldBe(null); + } + + [Theory] + [InlineData(@"foo", @"foo")] + [InlineData(@"foo\bar", @"foo/bar")] + [InlineData(@"foo/bar", @"foo/bar")] + [InlineData(@"foo\bar\", @"foo/bar")] + [InlineData(@"foo/bar/", @"foo/bar")] + [InlineData(@".\foo", @"foo")] + [InlineData(@"./foo", @"foo")] + [InlineData(@"foo\..\bar", @"foo/../bar")] + [InlineData(@"foo/../bar", @"foo/../bar")] + public void Should_Set_FilePath(string filePath, string expectedFilePath) + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InFile(filePath).Create(); + + // Then + issue.AffectedFileRelativePath.ToString().ShouldBe(expectedFilePath); + issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "File path was not set as relative."); + } + } + + public sealed class TheInFileLineMethod + { + [Fact] + public void Should_Throw_If_Line_Is_Negative() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", -1)); + + // Then + result.IsArgumentOutOfRangeException("line"); + } + + [Fact] + public void Should_Throw_If_Line_Is_Zero() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 0)); + + // Then + result.IsArgumentOutOfRangeException("line"); + } + + [Fact] + public void Should_Handle_Line_Which_Is_Null() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InFile("foo", null).Create(); + + // Then + issue.Line.ShouldBe(null); + } + + [Theory] + [InlineData(@"foo", @"foo")] + [InlineData(@"foo\bar", @"foo/bar")] + [InlineData(@"foo/bar", @"foo/bar")] + [InlineData(@"foo\bar\", @"foo/bar")] + [InlineData(@"foo/bar/", @"foo/bar")] + [InlineData(@".\foo", @"foo")] + [InlineData(@"./foo", @"foo")] + [InlineData(@"foo\..\bar", @"foo/../bar")] + [InlineData(@"foo/../bar", @"foo/../bar")] + public void Should_Set_FilePath(string filePath, string expectedFilePath) + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InFile(filePath, 10).Create(); + + // Then + issue.AffectedFileRelativePath.ToString().ShouldBe(expectedFilePath); + issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "File path was not set as relative."); + } + + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_Line(int line) + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.InFile("foo", line).Create(); + + // Then + issue.Line.ShouldBe(line); + } + } + + public sealed class TheInFileLineColumnMethod + { + [Fact] + public void Should_Throw_If_Line_Is_Negative() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", -1, 50)); + + // Then + result.IsArgumentOutOfRangeException("line"); + } + + [Fact] + public void Should_Throw_If_Line_Is_Zero() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 0, 50)); + + // Then + result.IsArgumentOutOfRangeException("line"); + } + + [Fact] + public void Should_Throw_If_Column_Is_Negative() + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 100, -1)); + + // Then + result.IsArgumentOutOfRangeException("column"); + } + + [Fact] + public void Should_Throw_If_Column_Is_Zero() { // Given var fixture = new IssueBuilderFixture(); - var projectPath = string.Empty; // When - var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 100, 0)); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("column"); } [Fact] - public void Should_Handle_Project_Path_Which_Are_WhiteSpace() + public void Should_Handle_Line_Which_Is_Null() { // Given var fixture = new IssueBuilderFixture(); - var projectPath = " "; // When - var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + var issue = fixture.IssueBuilder.InFile("foo", null, null).Create(); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + issue.Line.ShouldBe(null); } - [Theory] - [InlineData(@"src/project.csproj")] - public void Should_Set_ProjectFileRelativePath(string projectPath) + [Fact] + public void Should_Handle_Column_Which_Is_Null() { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InProjectFile(projectPath).Create(); + var issue = fixture.IssueBuilder.InFile("foo", 100, null).Create(); // Then - issue.ProjectFileRelativePath.ToString().ShouldBe(projectPath); + issue.Column.ShouldBe(null); } - } - public sealed class TheInProjectOfNameMethod - { - [Fact] - public void Should_Handle_Project_Names_Which_Are_Null() + [Theory] + [InlineData(@"foo", @"foo")] + [InlineData(@"foo\bar", @"foo/bar")] + [InlineData(@"foo/bar", @"foo/bar")] + [InlineData(@"foo\bar\", @"foo/bar")] + [InlineData(@"foo/bar/", @"foo/bar")] + [InlineData(@".\foo", @"foo")] + [InlineData(@"./foo", @"foo")] + [InlineData(@"foo\..\bar", @"foo/../bar")] + [InlineData(@"foo/../bar", @"foo/../bar")] + public void Should_Set_FilePath(string filePath, string expectedFilePath) { // Given var fixture = new IssueBuilderFixture(); - string projectName = null; // When - var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + var issue = fixture.IssueBuilder.InFile(filePath, 10, 50).Create(); // Then - issue.ProjectName.ShouldBe(projectName); + issue.AffectedFileRelativePath.ToString().ShouldBe(expectedFilePath); + issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "File path was not set as relative."); } - [Fact] - public void Should_Handle_Project_Names_Which_Are_Empty() + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_Line(int line) { // Given var fixture = new IssueBuilderFixture(); - var projectName = string.Empty; // When - var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + var issue = fixture.IssueBuilder.InFile("foo", line, 50).Create(); // Then - issue.ProjectName.ShouldBe(projectName); + issue.Line.ShouldBe(line); } - [Fact] - public void Should_Handle_Project_Names_Which_Are_WhiteSpace() + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_Column(int column) { // Given var fixture = new IssueBuilderFixture(); - var projectName = " "; // When - var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + var issue = fixture.IssueBuilder.InFile("foo", 100, column).Create(); // Then - issue.ProjectName.ShouldBe(projectName); + issue.Column.ShouldBe(column); } + } - [Theory] - [InlineData("project")] - public void Should_Set_ProjectName(string projectName) + public sealed class TheInFileLineRangeColumnRangeMethod + { + [Fact] + public void Should_Throw_If_StartLine_Is_Negative() { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InProjectOfName(projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", -1, 50, 1, 10)); // Then - issue.ProjectName.ShouldBe(projectName); + result.IsArgumentOutOfRangeException("startLine"); } - } - public sealed class TheInProjectMethod - { [Fact] - public void Should_Handle_Project_Paths_Which_Are_Null() + public void Should_Throw_If_StartLine_Is_Zero() { // Given var fixture = new IssueBuilderFixture(); - string projectPath = null; - var projectName = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 0, 50, 1, 10)); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("startLine"); } [Fact] - public void Should_Handle_Project_Paths_Which_Are_Empty() + public void Should_Throw_If_EndLine_Is_Negative() { // Given var fixture = new IssueBuilderFixture(); - var projectPath = string.Empty; - var projectName = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 5, -1, 1, 10)); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("endLine"); } [Fact] - public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() + public void Should_Throw_If_EndLine_Is_Zero() { // Given var fixture = new IssueBuilderFixture(); - var projectPath = " "; - var projectName = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 5, 0, 1, 10)); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("endLine"); } [Fact] - public void Should_Handle_Project_Names_Which_Are_Null() + public void Should_Throw_If_StartColumn_Is_Negative() { // Given var fixture = new IssueBuilderFixture(); - string projectName = null; - var projectPath = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 5, 50, -1, 10)); // Then - issue.ProjectName.ShouldBe(projectName); + result.IsArgumentOutOfRangeException("startColumn"); } [Fact] - public void Should_Handle_Project_Names_Which_Are_Empty() + public void Should_Throw_If_StartColumn_Is_Zero() { // Given var fixture = new IssueBuilderFixture(); - var projectName = string.Empty; - var projectPath = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 5, 50, 0, 10)); // Then - issue.ProjectName.ShouldBe(projectName); + result.IsArgumentOutOfRangeException("startColumn"); } [Fact] - public void Should_Handle_Project_Names_Which_Are_WhiteSpace() + public void Should_Throw_If_EndColumn_Is_Negative() { // Given var fixture = new IssueBuilderFixture(); - var projectName = " "; - var projectPath = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 5, 50, 1, -1)); // Then - issue.ProjectName.ShouldBe(projectName); + result.IsArgumentOutOfRangeException("endColumn"); } - [Theory] - [InlineData(@"src/project.csproj")] - public void Should_Set_ProjectFileRelativePath(string projectPath) + [Fact] + public void Should_Throw_If_EndColumn_Is_Zero() { // Given var fixture = new IssueBuilderFixture(); - var projectName = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var result = Record.Exception(() => + fixture.IssueBuilder.InFile("foo", 5, 50, 1, 0)); // Then - issue.ProjectFileRelativePath.ToString().ShouldBe(projectPath); + result.IsArgumentOutOfRangeException("endColumn"); } - [Theory] - [InlineData("project")] - public void Should_Set_ProjectName(string projectName) + [Fact] + public void Should_Handle_StartLine_Which_Is_Null() { // Given var fixture = new IssueBuilderFixture(); - var projectPath = "foo"; // When - var issue = fixture.IssueBuilder.InProject(projectPath, projectName).Create(); + var issue = fixture.IssueBuilder.InFile("foo", null, null, null, null).Create(); // Then - issue.ProjectName.ShouldBe(projectName); + issue.Line.ShouldBe(null); } - } - public sealed class TheInFileMethod - { [Fact] - public void Should_Handle_File_Paths_Which_Are_Null() + public void Should_Handle_EndLine_Which_Is_Null() { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile(null).Create(); + var issue = fixture.IssueBuilder.InFile("foo", 5, null, null, null).Create(); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + issue.EndLine.ShouldBe(null); } [Fact] - public void Should_Handle_File_Paths_Which_Are_Empty() + public void Should_Handle_StartColumn_Which_Is_Null() { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile(string.Empty).Create(); + var issue = fixture.IssueBuilder.InFile("foo", 5, 50, null, null).Create(); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + issue.Column.ShouldBe(null); } [Fact] - public void Should_Handle_File_Paths_Which_Are_WhiteSpace() + public void Should_Handle_EndColumn_Which_Is_Null() { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile(" ").Create(); + var issue = fixture.IssueBuilder.InFile("foo", 5, 50, 1, null).Create(); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + issue.EndColumn.ShouldBe(null); } [Theory] @@ -516,132 +1536,103 @@ public void Should_Set_FilePath(string filePath, string expectedFilePath) var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile(filePath).Create(); + var issue = fixture.IssueBuilder.InFile(filePath, 5, 50, 1, 10).Create(); // Then issue.AffectedFileRelativePath.ToString().ShouldBe(expectedFilePath); issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "File path was not set as relative."); } - } - public sealed class TheInFileLineMethod - { - [Fact] - public void Should_Handle_File_Paths_Which_Are_Null() + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_StartLine(int startLine) { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile(null).Create(); + var issue = fixture.IssueBuilder.InFile("foo", startLine, null, 1, 10).Create(); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + issue.Line.ShouldBe(startLine); } - [Fact] - public void Should_Handle_File_Paths_Which_Are_Empty() + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_EndLine(int endLine) { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile(string.Empty).Create(); + var issue = fixture.IssueBuilder.InFile("foo", 1, endLine, 1, 10).Create(); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + issue.EndLine.ShouldBe(endLine); } - [Fact] - public void Should_Handle_File_Paths_Which_Are_WhiteSpace() + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_StartColumn(int startColumn) { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile(" ").Create(); + var issue = fixture.IssueBuilder.InFile("foo", 5, 50, startColumn, null).Create(); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + issue.Column.ShouldBe(startColumn); } - [Fact] - public void Should_Throw_If_Line_Is_Negative() + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_EndColumn(int endColumn) { // Given var fixture = new IssueBuilderFixture(); // When - var result = Record.Exception(() => - fixture.IssueBuilder.InFile("foo", -1)); + var issue = fixture.IssueBuilder.InFile("foo", 5, 50, 1, endColumn).Create(); // Then - result.IsArgumentOutOfRangeException("line"); + issue.EndColumn.ShouldBe(endColumn); } + } + public sealed class TheWithFileLinkMethod + { [Fact] - public void Should_Throw_If_Line_Is_Zero() + public void Should_Throw_If_FileLink_Is_Null() { // Given var fixture = new IssueBuilderFixture(); + Uri fileLink = null; // When var result = Record.Exception(() => - fixture.IssueBuilder.InFile("foo", 0)); - - // Then - result.IsArgumentOutOfRangeException("line"); - } - - [Fact] - public void Should_Handle_Line_Which_Is_Null() - { - // Given - var fixture = new IssueBuilderFixture(); - - // When - var issue = fixture.IssueBuilder.InFile("foo", null).Create(); - - // Then - issue.Line.ShouldBe(null); - } - - [Theory] - [InlineData(@"foo", @"foo")] - [InlineData(@"foo\bar", @"foo/bar")] - [InlineData(@"foo/bar", @"foo/bar")] - [InlineData(@"foo\bar\", @"foo/bar")] - [InlineData(@"foo/bar/", @"foo/bar")] - [InlineData(@".\foo", @"foo")] - [InlineData(@"./foo", @"foo")] - [InlineData(@"foo\..\bar", @"foo/../bar")] - [InlineData(@"foo/../bar", @"foo/../bar")] - public void Should_Set_FilePath(string filePath, string expectedFilePath) - { - // Given - var fixture = new IssueBuilderFixture(); - - // When - var issue = fixture.IssueBuilder.InFile(filePath, 10).Create(); + fixture.IssueBuilder.WithFileLink(fileLink)); // Then - issue.AffectedFileRelativePath.ToString().ShouldBe(expectedFilePath); - issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "File path was not set as relative."); + result.IsArgumentNullException("fileLink"); } [Theory] - [InlineData(1)] - [InlineData(int.MaxValue)] - public void Should_Set_Line(int line) + [InlineData("https://google.com/")] + public void Should_Set_FileLink(string fileLink) { // Given var fixture = new IssueBuilderFixture(); // When - var issue = fixture.IssueBuilder.InFile("foo", line).Create(); + var issue = fixture.IssueBuilder.WithFileLink(new Uri(fileLink)).Create(); // Then - issue.Line.ShouldBe(line); + issue.FileLink.ToString().ShouldBe(fileLink); } } @@ -902,5 +1893,67 @@ public void Should_Set_RuleUrl(string ruleUri) issue.RuleUrl.ToString().ShouldBe(ruleUri); } } + + public sealed class TheForRunMethod + { + [Fact] + public void Should_Throw_If_Run_Is_Null() + { + // Given + var fixture = new IssueBuilderFixture(); + string run = null; + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.ForRun(run)); + + // Then + result.IsArgumentNullException("run"); + } + + [Fact] + public void Should_Throw_If_Run_Is_Empty() + { + // Given + var fixture = new IssueBuilderFixture(); + string run = string.Empty; + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.ForRun(run)); + + // Then + result.IsArgumentException("run"); + } + + [Fact] + public void Should_Throw_If_Run_Is_WhiteSpace() + { + // Given + var fixture = new IssueBuilderFixture(); + string run = " "; + + // When + var result = Record.Exception(() => + fixture.IssueBuilder.ForRun(run)); + + // Then + result.IsArgumentException("run"); + } + + [Theory] + [InlineData("run")] + public void Should_Set_Run(string run) + { + // Given + var fixture = new IssueBuilderFixture(); + + // When + var issue = fixture.IssueBuilder.ForRun(run).Create(); + + // Then + issue.Run.ToString().ShouldBe(run); + } + } } } \ No newline at end of file diff --git a/src/Cake.Issues.Tests/IssueReaderTests.cs b/src/Cake.Issues.Tests/IssueReaderTests.cs index 6a6696d62..de69051c1 100644 --- a/src/Cake.Issues.Tests/IssueReaderTests.cs +++ b/src/Cake.Issues.Tests/IssueReaderTests.cs @@ -288,6 +288,99 @@ public void Should_Read_Correct_Number_Of_Issues_From_Multiple_Providers() issues.ShouldContain(issue3); issues.ShouldContain(issue4); } + + [Fact] + public void Should_Set_Run_Property() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(@"src\Cake.Issues.Tests\FakeIssueProvider.cs", 10) + .OfRule("Foo") + .WithPriority(IssuePriority.Warning) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("Bar", "ProviderTypeBar", "ProviderNameBar") + .InFile(@"src\Cake.Issues.Tests\FakeIssueProvider.cs", 12) + .OfRule("Bar") + .WithPriority(IssuePriority.Warning) + .Create(); + var fixture = new IssuesFixture(); + fixture.IssueProviders.Clear(); + fixture.IssueProviders.Add( + new FakeIssueProvider( + fixture.Log, + new List + { + issue1, + issue2, + })); + var run = "Run"; + fixture.Settings.Run = run; + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + issues.ShouldContain(issue1); + issue1.Run.ShouldBe(run); + issues.ShouldContain(issue2); + issue2.Run.ShouldBe(run); + } + + [Fact] + public void Should_Set_FileLink_Property() + { + // Given + var filePath1 = @"src\Cake.Issues.Tests\Foo.cs"; + var line1 = 10; + var endLine1 = 12; + var issue1 = + IssueBuilder + .NewIssue("Foo", "ProviderTypeFoo", "ProviderNameFoo") + .InFile(filePath1, line1, endLine1, 1, 1) + .OfRule("Foo") + .WithPriority(IssuePriority.Warning) + .Create(); + var filePath2 = @"src\Cake.Issues.Tests\Bar.cs"; + var line2 = 12; + var issue2 = + IssueBuilder + .NewIssue("Bar", "ProviderTypeBar", "ProviderNameBar") + .InFile(filePath2, line2) + .OfRule("Bar") + .WithPriority(IssuePriority.Warning) + .Create(); + var fixture = new IssuesFixture(); + fixture.IssueProviders.Clear(); + fixture.IssueProviders.Add( + new FakeIssueProvider( + fixture.Log, + new List + { + issue1, + issue2, + })); + var repoUrl = "https://github.com/cake-contrib/Cake.Issues.Website"; + var branch = "develop"; + fixture.Settings.FileLinkSettings = + FileLinkSettings.ForGitHub(new System.Uri(repoUrl)).Branch(branch); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + issues.ShouldContain(issue1); + issue1.FileLink.ToString() + .ShouldBe($"{repoUrl}/blob/{branch}/{filePath1.Replace(@"\", "/")}#L{line1}-L{endLine1}"); + issues.ShouldContain(issue2); + issue2.FileLink.ToString() + .ShouldBe($"{repoUrl}/blob/{branch}/{filePath2.Replace(@"\", "/")}#L{line2}"); + } } } } \ No newline at end of file diff --git a/src/Cake.Issues.Tests/IssueTests.cs b/src/Cake.Issues.Tests/IssueTests.cs index 5a5d9b7ea..5286a4aeb 100644 --- a/src/Cake.Issues.Tests/IssueTests.cs +++ b/src/Cake.Issues.Tests/IssueTests.cs @@ -9,6 +9,213 @@ public sealed class IssueTests { public sealed class TheCtor { + public sealed class TheIdentifierArgument + { + [Fact] + public void Should_Throw_If_Identifier_Is_Null() + { + // Given + string identifier = null; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentNullException("identifier"); + } + + [Fact] + public void Should_Throw_If_Identifier_Is_Empty() + { + // Given + var identifier = string.Empty; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("identifier"); + } + + [Fact] + public void Should_Throw_If_Identifier_Is_WhiteSpace() + { + // Given + var identifier = " "; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("identifier"); + } + + [Theory] + [InlineData("identifier")] + public void Should_Set_Identifier(string identifier) + { + // Given + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.Identifier.ShouldBe(identifier); + } + } + public sealed class TheProjectFileRelativePathArgument { [Theory] @@ -16,9 +223,1162 @@ public sealed class TheProjectFileRelativePathArgument public void Should_Throw_If_Project_Path_Is_Invalid(string projectPath) { // Given + var identifier = "identifier"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentException("projectFileRelativePath"); + } + + [Theory] + [InlineData(@"c:\src\foo.cs")] + [InlineData(@"/foo")] + [InlineData(@"\foo")] + public void Should_Throw_If_File_Path_Is_Absolute(string projectPath) + { + // Given + var identifier = "identifier"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("projectFileRelativePath"); + } + + [Fact] + public void Should_Handle_Project_Paths_Which_Are_Null() + { + // Given + var identifier = "identifier"; + string projectPath = null; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_Project_Paths_Which_Are_Empty() + { + // Given + var identifier = "identifier"; + var projectPath = string.Empty; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() + { + // Given + var identifier = "identifier"; + var projectPath = " "; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectFileRelativePath.ShouldBe(null); + } + + [Theory] + [InlineData("project")] + public void Should_Set_ProjectFileRelativePath(string projectPath) + { + // Given + var identifier = "identifier"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectFileRelativePath.ToString().ShouldBe(projectPath); + } + } + + public sealed class TheProjectNameArgument + { + [Fact] + public void Should_Handle_Projects_Which_Are_Null() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + string projectName = null; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Fact] + public void Should_Handle_Projects_Which_Are_Empty() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = string.Empty; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Fact] + public void Should_Handle_Projects_Which_Are_WhiteSpace() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = " "; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + + [Theory] + [InlineData("project")] + public void Should_Set_ProjectName(string projectName) + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.ProjectName.ShouldBe(projectName); + } + } + + public sealed class TheAffectedFileRelativePathArgument + { + [Theory] + [InlineData("foo\tbar")] + public void Should_Throw_If_File_Path_Is_Invalid(string filePath) + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentException("affectedFileRelativePath"); + } + + [Theory] + [InlineData(@"c:\src\foo.cs")] + [InlineData(@"/foo")] + [InlineData(@"\foo")] + public void Should_Throw_If_File_Path_Is_Absolute(string filePath) + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("affectedFileRelativePath"); + } + + [Fact] + public void Should_Handle_File_Paths_Which_Are_Null() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + string filePath = null; + int? line = null; + int? endLine = null; + int? column = null; + int? endColumn = null; + Uri fileLink = null; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.AffectedFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_File_Paths_Which_Are_Empty() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = string.Empty; + int? line = null; + int? endLine = null; + int? column = null; + int? endColumn = null; + Uri fileLink = null; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.AffectedFileRelativePath.ShouldBe(null); + } + + [Fact] + public void Should_Handle_File_Paths_Which_Are_WhiteSpace() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = " "; + int? line = null; + int? endLine = null; + int? column = null; + int? endColumn = null; + Uri fileLink = null; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.AffectedFileRelativePath.ShouldBe(null); + } + + [Theory] + [InlineData(@"foo", @"foo")] + [InlineData(@"foo\bar", @"foo/bar")] + [InlineData(@"foo/bar", @"foo/bar")] + [InlineData(@"foo\bar\", @"foo/bar")] + [InlineData(@"foo/bar/", @"foo/bar")] + [InlineData(@".\foo", @"foo")] + [InlineData(@"./foo", @"foo")] + [InlineData(@"foo\..\bar", @"foo/../bar")] + [InlineData(@"foo/../bar", @"foo/../bar")] + public void Should_Set_File_Path(string filePath, string expectedFilePath) + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.AffectedFileRelativePath.ToString().ShouldBe(expectedFilePath); + issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "File path was not set as relative."); + } + } + + public sealed class TheLineArgument + { + [Fact] + public void Should_Throw_If_Line_Is_Negative() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = -1; + int? endLine = null; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("line"); + } + + [Fact] + public void Should_Throw_If_Line_Is_Zero() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 0; + int? endLine = null; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("line"); + } + + [Fact] + public void Should_Throw_If_Line_Is_Set_But_No_File() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + string filePath = null; + var line = 10; + int? endLine = null; + int? column = null; + int? endColumn = null; + Uri fileLink = null; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("line"); + } + + [Fact] + public void Should_Handle_Line_Which_Is_Null() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + int? line = null; + int? endLine = null; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.Line.ShouldBe(line); + } + + [Theory] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_Line(int line) + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + int? endLine = null; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.Line.ShouldBe(line); + } + } + + public sealed class TheEndLineArgument + { + [Fact] + public void Should_Throw_If_EndLine_Is_Negative() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = -1; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + var run = "Run"; + + // When + var result = Record.Exception(() => + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName)); + + // Then + result.IsArgumentOutOfRangeException("endLine"); + } + + [Fact] + public void Should_Throw_If_EndLine_Is_Zero() + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; - var line = 100; + var line = 10; + var endLine = 0; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -28,14 +1388,20 @@ public void Should_Throw_If_Project_Path_Is_Invalid(string projectPath) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -43,23 +1409,27 @@ public void Should_Throw_If_Project_Path_Is_Invalid(string projectPath) priorityName, rule, ruleUri, + run, providerType, providerName)); // Then - result.IsArgumentException("projectFileRelativePath"); + result.IsArgumentOutOfRangeException("endLine"); } - [Theory] - [InlineData(@"c:\src\foo.cs")] - [InlineData(@"/foo")] - [InlineData(@"\foo")] - public void Should_Throw_If_File_Path_Is_Absolute(string projectPath) + [Fact] + public void Should_Throw_If_EndLine_Is_Set_But_No_Line() { // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; - var line = 100; + int? line = null; + var endLine = 12; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -69,14 +1439,20 @@ public void Should_Throw_If_File_Path_Is_Absolute(string projectPath) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -84,21 +1460,27 @@ public void Should_Throw_If_File_Path_Is_Absolute(string projectPath) priorityName, rule, ruleUri, + run, providerType, providerName)); // Then - result.IsArgumentOutOfRangeException("projectFileRelativePath"); + result.IsArgumentOutOfRangeException("endLine"); } [Fact] - public void Should_Handle_Project_Paths_Which_Are_Null() + public void Should_Throw_If_EndLine_Is_Smaller_Line() { // Given - string projectPath = null; + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; - var line = 10; + var line = 100; + var endLine = 12; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -108,14 +1490,20 @@ public void Should_Handle_Project_Paths_Which_Are_Null() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var issue = + var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -123,21 +1511,27 @@ public void Should_Handle_Project_Paths_Which_Are_Null() priorityName, rule, ruleUri, + run, providerType, - providerName); + providerName)); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("endLine"); } [Fact] - public void Should_Handle_Project_Paths_Which_Are_Empty() + public void Should_Handle_EndLine_Which_Is_Null() { // Given - var projectPath = string.Empty; + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + int? endLine = null; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -147,14 +1541,20 @@ public void Should_Handle_Project_Paths_Which_Are_Empty() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -162,21 +1562,27 @@ public void Should_Handle_Project_Paths_Which_Are_Empty() priorityName, rule, ruleUri, + run, providerType, providerName); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + issue.EndLine.ShouldBe(endLine); } [Fact] - public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() + public void Should_Handle_EndLine_Which_Is_Equals_Line() { // Given - var projectPath = " "; + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 10; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -186,14 +1592,20 @@ public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -201,21 +1613,28 @@ public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() priorityName, rule, ruleUri, + run, providerType, providerName); // Then - issue.ProjectFileRelativePath.ShouldBe(null); + issue.EndLine.ShouldBe(endLine); } [Theory] - [InlineData("project")] - public void Should_Set_ProjectFileRelativePath(string projectPath) + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_EndLine(int endLine) { // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; - var line = 10; + var line = 1; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -225,14 +1644,20 @@ public void Should_Set_ProjectFileRelativePath(string projectPath) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -240,24 +1665,30 @@ public void Should_Set_ProjectFileRelativePath(string projectPath) priorityName, rule, ruleUri, + run, providerType, providerName); // Then - issue.ProjectFileRelativePath.ToString().ShouldBe(projectPath); + issue.EndLine.ShouldBe(endLine); } } - public sealed class TheProjectNameArgument + public sealed class TheColumnArgument { [Fact] - public void Should_Handle_Projects_Which_Are_Null() + public void Should_Throw_If_Column_Is_Negative() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; - string projectName = null; + var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = -1; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -267,14 +1698,20 @@ public void Should_Handle_Projects_Which_Are_Null() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var issue = + var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -282,21 +1719,27 @@ public void Should_Handle_Projects_Which_Are_Null() priorityName, rule, ruleUri, + run, providerType, - providerName); + providerName)); // Then - issue.ProjectName.ShouldBe(projectName); + result.IsArgumentOutOfRangeException("column"); } [Fact] - public void Should_Handle_Projects_Which_Are_Empty() + public void Should_Throw_If_Column_Is_Zero() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; - var projectName = string.Empty; + var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 0; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -306,14 +1749,20 @@ public void Should_Handle_Projects_Which_Are_Empty() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var issue = + var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -321,21 +1770,27 @@ public void Should_Handle_Projects_Which_Are_Empty() priorityName, rule, ruleUri, + run, providerType, - providerName); + providerName)); // Then - issue.ProjectName.ShouldBe(projectName); + result.IsArgumentOutOfRangeException("column"); } [Fact] - public void Should_Handle_Projects_Which_Are_WhiteSpace() + public void Should_Throw_If_Column_Is_Set_But_No_Line() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; - var projectName = " "; + var projectName = "foo"; var filePath = @"src\foo.cs"; - var line = 10; + int? line = null; + int? endLine = null; + var column = 50; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -345,14 +1800,20 @@ public void Should_Handle_Projects_Which_Are_WhiteSpace() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var issue = + var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -360,21 +1821,27 @@ public void Should_Handle_Projects_Which_Are_WhiteSpace() priorityName, rule, ruleUri, + run, providerType, - providerName); + providerName)); // Then - issue.ProjectName.ShouldBe(projectName); + result.IsArgumentOutOfRangeException("column"); } - [Theory] - [InlineData("project")] - public void Should_Set_ProjectName(string projectName) + [Fact] + public void Should_Handle_Column_Which_Is_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; + var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -384,14 +1851,20 @@ public void Should_Set_ProjectName(string projectName) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -399,24 +1872,29 @@ public void Should_Set_ProjectName(string projectName) priorityName, rule, ruleUri, + run, providerType, providerName); // Then - issue.ProjectName.ShouldBe(projectName); + issue.Column.ShouldBe(column); } - } - public sealed class TheAffectedFileRelativePathArgument - { [Theory] - [InlineData("foo\tbar")] - public void Should_Throw_If_File_Path_Is_Invalid(string filePath) + [InlineData(null)] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_Column(int? column) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; - var line = 100; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -426,14 +1904,20 @@ public void Should_Throw_If_File_Path_Is_Invalid(string filePath) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var result = Record.Exception(() => + var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -441,23 +1925,30 @@ public void Should_Throw_If_File_Path_Is_Invalid(string filePath) priorityName, rule, ruleUri, + run, providerType, - providerName)); + providerName); // Then - result.IsArgumentException("affectedFileRelativePath"); + issue.Column.ShouldBe(column); } + } - [Theory] - [InlineData(@"c:\src\foo.cs")] - [InlineData(@"/foo")] - [InlineData(@"\foo")] - public void Should_Throw_If_File_Path_Is_Absolute(string filePath) + public sealed class TheEndColumnArgument + { + [Fact] + public void Should_Throw_If_EndColumn_Is_Negative() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; - var line = 100; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = -1; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -467,14 +1958,20 @@ public void Should_Throw_If_File_Path_Is_Absolute(string filePath) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -482,21 +1979,27 @@ public void Should_Throw_If_File_Path_Is_Absolute(string filePath) priorityName, rule, ruleUri, + run, providerType, providerName)); // Then - result.IsArgumentOutOfRangeException("affectedFileRelativePath"); + result.IsArgumentOutOfRangeException("endColumn"); } [Fact] - public void Should_Handle_File_Paths_Which_Are_Null() + public void Should_Throw_If_EndColumn_Is_Zero() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; - string filePath = null; - int? line = null; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 0; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -506,14 +2009,20 @@ public void Should_Handle_File_Paths_Which_Are_Null() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var issue = + var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -521,21 +2030,27 @@ public void Should_Handle_File_Paths_Which_Are_Null() priorityName, rule, ruleUri, + run, providerType, - providerName); + providerName)); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("endColumn"); } [Fact] - public void Should_Handle_File_Paths_Which_Are_Empty() + public void Should_Throw_If_EndColumn_Is_Set_But_No_Column() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; - var filePath = string.Empty; - int? line = null; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + int? column = null; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -545,14 +2060,20 @@ public void Should_Handle_File_Paths_Which_Are_Empty() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var issue = + var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -560,21 +2081,27 @@ public void Should_Handle_File_Paths_Which_Are_Empty() priorityName, rule, ruleUri, + run, providerType, - providerName); + providerName)); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("endColumn"); } [Fact] - public void Should_Handle_File_Paths_Which_Are_WhiteSpace() + public void Should_Throw_If_EndColumn_Is_Smaller_Column() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; - var filePath = " "; - int? line = null; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 5; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -584,14 +2111,20 @@ public void Should_Handle_File_Paths_Which_Are_WhiteSpace() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var issue = + var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -599,29 +2132,27 @@ public void Should_Handle_File_Paths_Which_Are_WhiteSpace() priorityName, rule, ruleUri, + run, providerType, - providerName); + providerName)); // Then - issue.AffectedFileRelativePath.ShouldBe(null); + result.IsArgumentOutOfRangeException("endColumn"); } - [Theory] - [InlineData(@"foo", @"foo")] - [InlineData(@"foo\bar", @"foo/bar")] - [InlineData(@"foo/bar", @"foo/bar")] - [InlineData(@"foo\bar\", @"foo/bar")] - [InlineData(@"foo/bar/", @"foo/bar")] - [InlineData(@".\foo", @"foo")] - [InlineData(@"./foo", @"foo")] - [InlineData(@"foo\..\bar", @"foo/../bar")] - [InlineData(@"foo/../bar", @"foo/../bar")] - public void Should_Set_File_Path(string filePath, string expectedFilePath) + [Fact] + public void Should_Handle_EndColumn_Which_Is_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; + var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + int? column = null; + int? endColumn = null; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -631,14 +2162,20 @@ public void Should_Set_File_Path(string filePath, string expectedFilePath) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -646,25 +2183,27 @@ public void Should_Set_File_Path(string filePath, string expectedFilePath) priorityName, rule, ruleUri, + run, providerType, providerName); // Then - issue.AffectedFileRelativePath.ToString().ShouldBe(expectedFilePath); - issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "File path was not set as relative."); + issue.EndColumn.ShouldBe(endColumn); } - } - public sealed class TheLineArgument - { [Fact] - public void Should_Throw_If_Line_Is_Negative() + public void Should_Handle_EndColumn_Which_Is_Equals_Column() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; - var line = -1; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 50; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -674,14 +2213,20 @@ public void Should_Throw_If_Line_Is_Negative() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var result = Record.Exception(() => + var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -689,21 +2234,29 @@ public void Should_Throw_If_Line_Is_Negative() priorityName, rule, ruleUri, + run, providerType, - providerName)); + providerName); // Then - result.IsArgumentOutOfRangeException("line"); + issue.EndColumn.ShouldBe(endColumn); } - [Fact] - public void Should_Throw_If_Line_Is_Zero() + [Theory] + [InlineData(null)] + [InlineData(1)] + [InlineData(int.MaxValue)] + public void Should_Set_EndColumn(int? endColumn) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; - var line = 0; + var line = 10; + var endLine = 12; + var column = 1; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -713,14 +2266,20 @@ public void Should_Throw_If_Line_Is_Zero() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var result = Record.Exception(() => + var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -728,21 +2287,30 @@ public void Should_Throw_If_Line_Is_Zero() priorityName, rule, ruleUri, + run, providerType, - providerName)); + providerName); // Then - result.IsArgumentOutOfRangeException("line"); + issue.EndColumn.ShouldBe(endColumn); } + } + public sealed class TheFileLinkArgument + { [Fact] - public void Should_Throw_If_Line_Is_Set_But_No_File() + public void Should_Set_FileLink() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; - string filePath = null; + var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -752,14 +2320,20 @@ public void Should_Throw_If_Line_Is_Set_But_No_File() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When - var result = Record.Exception(() => + var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -767,23 +2341,27 @@ public void Should_Throw_If_Line_Is_Set_But_No_File() priorityName, rule, ruleUri, + run, providerType, - providerName)); + providerName); // Then - result.IsArgumentOutOfRangeException("line"); + issue.FileLink.ShouldBe(fileLink); } - [Theory] - [InlineData(null)] - [InlineData(1)] - [InlineData(int.MaxValue)] - public void Should_Set_Line(int? line) + [Fact] + public void Should_Set_FileLink_If_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + Uri fileLink = null; var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -793,14 +2371,20 @@ public void Should_Set_Line(int? line) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -808,11 +2392,12 @@ public void Should_Set_Line(int? line) priorityName, rule, ruleUri, + run, providerType, providerName); // Then - issue.Line.ShouldBe(line); + issue.FileLink.ShouldBe(fileLink); } } @@ -822,10 +2407,15 @@ public sealed class TheMessageTextArgument public void Should_Throw_If_MessageText_Is_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); string messageText = null; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -835,14 +2425,20 @@ public void Should_Throw_If_MessageText_Is_Null() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -850,6 +2446,7 @@ public void Should_Throw_If_MessageText_Is_Null() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -861,10 +2458,15 @@ public void Should_Throw_If_MessageText_Is_Null() public void Should_Throw_If_MessageText_Is_Empty() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = string.Empty; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -874,14 +2476,20 @@ public void Should_Throw_If_MessageText_Is_Empty() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -889,6 +2497,7 @@ public void Should_Throw_If_MessageText_Is_Empty() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -900,10 +2509,15 @@ public void Should_Throw_If_MessageText_Is_Empty() public void Should_Throw_If_MessageText_Is_WhiteSpace() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = " "; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -913,14 +2527,20 @@ public void Should_Throw_If_MessageText_Is_WhiteSpace() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -928,6 +2548,7 @@ public void Should_Throw_If_MessageText_Is_WhiteSpace() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -940,10 +2561,15 @@ public void Should_Throw_If_MessageText_Is_WhiteSpace() public void Should_Set_MessageText(string messageText) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; var priority = 1; @@ -952,14 +2578,20 @@ public void Should_Set_MessageText(string messageText) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -967,6 +2599,7 @@ public void Should_Set_MessageText(string messageText) priorityName, rule, ruleUri, + run, providerType, providerName); @@ -985,10 +2618,15 @@ public sealed class TheMessageHtmlArgument public void Should_Set_MessageHtml(string messageHtml) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageMarkdown = "MessageMarkdown"; var priority = 1; @@ -997,14 +2635,20 @@ public void Should_Set_MessageHtml(string messageHtml) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1012,6 +2656,7 @@ public void Should_Set_MessageHtml(string messageHtml) priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1030,10 +2675,15 @@ public sealed class TheMessageMarkdownArgument public void Should_Set_MessageHtml(string messageMarkdown) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var priority = 1; @@ -1042,14 +2692,20 @@ public void Should_Set_MessageHtml(string messageMarkdown) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1057,6 +2713,7 @@ public void Should_Set_MessageHtml(string messageMarkdown) priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1077,10 +2734,15 @@ public sealed class ThePriorityArgument public void Should_Set_Priority(int? priority) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1089,14 +2751,20 @@ public void Should_Set_Priority(int? priority) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1104,6 +2772,7 @@ public void Should_Set_Priority(int? priority) priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1118,10 +2787,15 @@ public sealed class ThePriorityNameArgument public void Should_Handle_PriorityNames_Which_Are_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1131,14 +2805,20 @@ public void Should_Handle_PriorityNames_Which_Are_Null() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1146,6 +2826,7 @@ public void Should_Handle_PriorityNames_Which_Are_Null() priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1157,10 +2838,15 @@ public void Should_Handle_PriorityNames_Which_Are_Null() public void Should_Handle_PriorityNames_Which_Are_Empty() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1170,14 +2856,20 @@ public void Should_Handle_PriorityNames_Which_Are_Empty() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1185,6 +2877,7 @@ public void Should_Handle_PriorityNames_Which_Are_Empty() priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1196,10 +2889,15 @@ public void Should_Handle_PriorityNames_Which_Are_Empty() public void Should_Handle_PriorityNames_Which_Are_WhiteSpace() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1209,14 +2907,20 @@ public void Should_Handle_PriorityNames_Which_Are_WhiteSpace() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1224,6 +2928,7 @@ public void Should_Handle_PriorityNames_Which_Are_WhiteSpace() priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1236,10 +2941,15 @@ public void Should_Handle_PriorityNames_Which_Are_WhiteSpace() public void Should_Set_Priority_Name(string priorityName) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1248,14 +2958,20 @@ public void Should_Set_Priority_Name(string priorityName) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1263,6 +2979,7 @@ public void Should_Set_Priority_Name(string priorityName) priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1280,10 +2997,15 @@ public sealed class TheRuleArgument public void Should_Set_Rule(string rule) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1292,14 +3014,20 @@ public void Should_Set_Rule(string rule) var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1307,6 +3035,7 @@ public void Should_Set_Rule(string rule) priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1321,10 +3050,15 @@ public sealed class TheRuleUrlArgument public void Should_Set_Rule_Url() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1334,14 +3068,20 @@ public void Should_Set_Rule_Url() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1349,6 +3089,7 @@ public void Should_Set_Rule_Url() priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1360,10 +3101,15 @@ public void Should_Set_Rule_Url() public void Should_Set_Rule_Url_If_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1373,14 +3119,20 @@ public void Should_Set_Rule_Url_If_Null() Uri ruleUri = null; var providerType = "ProviderType"; var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1388,6 +3140,7 @@ public void Should_Set_Rule_Url_If_Null() priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1396,16 +3149,77 @@ public void Should_Set_Rule_Url_If_Null() } } + public sealed class TheRunArgument + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("run")] + public void Should_Set_Run(string run) + { + // Given + var identifier = "identifier"; + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var issue = + new Issue( + identifier, + projectPath, + projectName, + filePath, + line, + endLine, + column, + endColumn, + fileLink, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + run, + providerType, + providerName); + + // Then + issue.Run.ShouldBe(run); + } + } + public sealed class TheProviderTypeArgument { [Fact] public void Should_Throw_If_Provider_Type_Is_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1415,14 +3229,20 @@ public void Should_Throw_If_Provider_Type_Is_Null() var ruleUri = new Uri("https://google.com"); string providerType = null; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1430,6 +3250,7 @@ public void Should_Throw_If_Provider_Type_Is_Null() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -1441,10 +3262,15 @@ public void Should_Throw_If_Provider_Type_Is_Null() public void Should_Throw_If_Provider_Type_Is_Empty() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1454,14 +3280,20 @@ public void Should_Throw_If_Provider_Type_Is_Empty() var ruleUri = new Uri("https://google.com"); var providerType = string.Empty; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1469,6 +3301,7 @@ public void Should_Throw_If_Provider_Type_Is_Empty() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -1480,10 +3313,15 @@ public void Should_Throw_If_Provider_Type_Is_Empty() public void Should_Throw_If_Provider_Type_Is_WhiteSpace() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1493,14 +3331,20 @@ public void Should_Throw_If_Provider_Type_Is_WhiteSpace() var ruleUri = new Uri("https://google.com"); var providerType = " "; var providerName = "ProviderName"; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1508,6 +3352,7 @@ public void Should_Throw_If_Provider_Type_Is_WhiteSpace() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -1520,10 +3365,15 @@ public void Should_Throw_If_Provider_Type_Is_WhiteSpace() public void Should_Set_ProviderType(string providerType) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1532,14 +3382,20 @@ public void Should_Set_ProviderType(string providerType) var rule = "Rule"; var ruleUri = new Uri("https://google.com"); var providerName = "ProviderName"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1547,6 +3403,7 @@ public void Should_Set_ProviderType(string providerType) priorityName, rule, ruleUri, + run, providerType, providerName); @@ -1561,10 +3418,15 @@ public sealed class TheProviderNameArgument public void Should_Throw_If_Provider_Name_Is_Null() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1574,14 +3436,20 @@ public void Should_Throw_If_Provider_Name_Is_Null() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; string providerName = null; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1589,6 +3457,7 @@ public void Should_Throw_If_Provider_Name_Is_Null() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -1600,10 +3469,15 @@ public void Should_Throw_If_Provider_Name_Is_Null() public void Should_Throw_If_Provider_Name_Is_Empty() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1613,14 +3487,20 @@ public void Should_Throw_If_Provider_Name_Is_Empty() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = string.Empty; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1628,6 +3508,7 @@ public void Should_Throw_If_Provider_Name_Is_Empty() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -1639,10 +3520,15 @@ public void Should_Throw_If_Provider_Name_Is_Empty() public void Should_Throw_If_Provider_Name_Is_WhiteSpace() { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1652,14 +3538,20 @@ public void Should_Throw_If_Provider_Name_Is_WhiteSpace() var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; var providerName = " "; + var run = "Run"; // When var result = Record.Exception(() => new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1667,6 +3559,7 @@ public void Should_Throw_If_Provider_Name_Is_WhiteSpace() priorityName, rule, ruleUri, + run, providerType, providerName)); @@ -1679,10 +3572,15 @@ public void Should_Throw_If_Provider_Name_Is_WhiteSpace() public void Should_Set_ProviderName(string providerName) { // Given + var identifier = "identifier"; var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var endLine = 12; + var column = 50; + var endColumn = 55; + var fileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); var messageText = "MessageText"; var messageHtml = "MessageHtml"; var messageMarkdown = "MessageMarkdown"; @@ -1691,14 +3589,20 @@ public void Should_Set_ProviderName(string providerName) var rule = "Rule"; var ruleUri = new Uri("https://google.com"); var providerType = "ProviderType"; + var run = "Run"; // When var issue = new Issue( + identifier, projectPath, projectName, filePath, line, + endLine, + column, + endColumn, + fileLink, messageText, messageHtml, messageMarkdown, @@ -1706,6 +3610,7 @@ public void Should_Set_ProviderName(string providerName) priorityName, rule, ruleUri, + run, providerType, providerName); diff --git a/src/Cake.Issues.Tests/IssuesFixture.cs b/src/Cake.Issues.Tests/IssuesFixture.cs index eecba2799..810ca5076 100644 --- a/src/Cake.Issues.Tests/IssuesFixture.cs +++ b/src/Cake.Issues.Tests/IssuesFixture.cs @@ -20,7 +20,7 @@ public IssuesFixture() public IList IssueProviders { get; set; } - public RepositorySettings Settings { get; set; } + public ReadIssuesSettings Settings { get; set; } public IEnumerable ReadIssues() { diff --git a/src/Cake.Issues.Tests/Serialization/IssueDeserializationExtensionsTests.cs b/src/Cake.Issues.Tests/Serialization/IssueDeserializationExtensionsTests.cs index f2ad8facb..2f24d8c0b 100644 --- a/src/Cake.Issues.Tests/Serialization/IssueDeserializationExtensionsTests.cs +++ b/src/Cake.Issues.Tests/Serialization/IssueDeserializationExtensionsTests.cs @@ -128,6 +128,32 @@ public void Should_Return_IssueV2() .OfRule("Rule", new Uri("https://google.com")) .WithPriority(IssuePriority.Warning)); } + + [Fact] + public void Should_Return_IssueV3() + { + // Given + var filePath = new FilePath("Testfiles/issueV3.json"); + + // When + var result = filePath.DeserializeToIssue(); + + // Then + IssueChecker.Check( + result, + IssueBuilder.NewIssue( + "Identifier", + "Something went wrong.", + "TestProvider", + "Test Provider") + .ForRun("TestRun") + .WithMessageInHtmlFormat("Something went wrong.") + .WithMessageInMarkdownFormat("Something went **wrong**.") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar.cs", 42, 420, 23, 230) + .OfRule("Rule", new Uri("https://google.com")) + .WithPriority(IssuePriority.Warning)); + } } public sealed class TheDeserializeToIssuesExtensionForAJsonFile @@ -225,6 +251,44 @@ public void Should_Return_List_Of_IssuesV2() .InFile(@"src\Foo\Bar2.cs") .WithPriority(IssuePriority.Warning)); } + + [Fact] + public void Should_Return_List_Of_IssuesV3() + { + // Given + var filePath = new FilePath("Testfiles/issuesV3.json"); + + // When + var result = filePath.DeserializeToIssues().ToList(); + + // Then + result.Count.ShouldBe(2); + IssueChecker.Check( + result[0], + IssueBuilder.NewIssue( + "Identifier1", + "Something went wrong.", + "TestProvider", + "Test Provider") + .WithMessageInHtmlFormat("Something went wrong.") + .WithMessageInMarkdownFormat("Something went **wrong**.") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar.cs", 42, 420, 23, 230) + .OfRule("Rule", new Uri("https://google.com")) + .WithPriority(IssuePriority.Warning)); + IssueChecker.Check( + result[1], + IssueBuilder.NewIssue( + "Identifier2", + "Something went wrong again.", + "TestProvider", + "Test Provider") + .WithMessageInHtmlFormat("Something went wrong again.") + .WithMessageInMarkdownFormat("Something went **wrong** again.") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar2.cs") + .WithPriority(IssuePriority.Warning)); + } } } } \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Serialization/IssueSerializationExtensionsTests.cs b/src/Cake.Issues.Tests/Serialization/IssueSerializationExtensionsTests.cs index a280ff2af..244117bb0 100644 --- a/src/Cake.Issues.Tests/Serialization/IssueSerializationExtensionsTests.cs +++ b/src/Cake.Issues.Tests/Serialization/IssueSerializationExtensionsTests.cs @@ -26,6 +26,23 @@ public void Should_Throw_If_Issue_Is_Null() result.IsArgumentNullException("issue"); } + [Fact] + public void Should_Give_Correct_Result_For_Identifier_After_Roundtrip() + { + // Given + var identifier = "identifier"; + var issue = + IssueBuilder + .NewIssue(identifier, "message", "providerType", "providerName") + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.Identifier.ShouldBe(identifier); + } + [Fact] public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() { @@ -113,6 +130,24 @@ public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() result.ProviderName.ShouldBe(providerName); } + [Fact] + public void Should_Give_Correct_Result_For_Run_After_Roundtrip() + { + // Given + var run = "run"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .ForRun(run) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.Run.ShouldBe(run); + } + [Fact] public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() { @@ -185,6 +220,78 @@ public void Should_Give_Correct_Result_For_Line_After_Roundtrip() result.Line.ShouldBe(line); } + [Fact] + public void Should_Give_Correct_Result_For_EndLine_After_Roundtrip() + { + // Given + var endLine = 420; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", 42, endLine, null, null) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.EndLine.ShouldBe(endLine); + } + + [Fact] + public void Should_Give_Correct_Result_For_Column_After_Roundtrip() + { + // Given + var column = 23; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", 42, column) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.Column.ShouldBe(column); + } + + [Fact] + public void Should_Give_Correct_Result_For_EndColumn_After_Roundtrip() + { + // Given + var endColumn = 230; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", 42, 420, 23, endColumn) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.EndColumn.ShouldBe(endColumn); + } + + [Fact] + public void Should_Give_Correct_Result_For_FileLink_After_Roundtrip() + { + // Given + var fileLink = "https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithFileLink(new Uri(fileLink)) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.FileLink.ToString().ShouldBe(fileLink); + } + [Fact] public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() { @@ -273,6 +380,32 @@ public void Should_Throw_If_Issue_Is_Null() result.IsArgumentNullException("issues"); } + [Fact] + public void Should_Give_Correct_Result_For_Identifier_After_Roundtrip() + { + // Given + var identifier1 = "identifier1"; + var identifier2 = "identifier2"; + var issues = + new List + { + IssueBuilder + .NewIssue(identifier1, "messageText1", "providerType1", "providerName1") + .Create(), + IssueBuilder + .NewIssue(identifier2, "messageText2", "providerType2", "providerName2") + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Identifier.ShouldBe(identifier1); + result.Last().Identifier.ShouldBe(identifier2); + } + [Fact] public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() { @@ -407,6 +540,34 @@ public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() result.Last().ProviderName.ShouldBe(providerName2); } + [Fact] + public void Should_Give_Correct_Result_For_Run_After_Roundtrip() + { + // Given + var run1 = "run1"; + var run2 = "run2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .ForRun(run1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .ForRun(run2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Run.ShouldBe(run1); + result.Last().Run.ShouldBe(run2); + } + [Fact] public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() { @@ -519,6 +680,118 @@ public void Should_Give_Correct_Result_For_Line_After_Roundtrip() result.Last().Line.ShouldBe(line2); } + [Fact] + public void Should_Give_Correct_Result_For_EndLine_After_Roundtrip() + { + // Given + var endLine1 = 230; + var endLine2 = 420; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", 23, endLine1, null, null) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", 42, endLine2, null, null) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().EndLine.ShouldBe(endLine1); + result.Last().EndLine.ShouldBe(endLine2); + } + + [Fact] + public void Should_Give_Correct_Result_For_Column_After_Roundtrip() + { + // Given + var column1 = 23; + var column2 = 42; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", 123, column1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", 123, column2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Column.ShouldBe(column1); + result.Last().Column.ShouldBe(column2); + } + + [Fact] + public void Should_Give_Correct_Result_For_EndColumn_After_Roundtrip() + { + // Given + var endColumn1 = 230; + var endColumn2 = 420; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", 5, 50, 23, endColumn1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", 5, 50, 42, endColumn2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().EndColumn.ShouldBe(endColumn1); + result.Last().EndColumn.ShouldBe(endColumn2); + } + + [Fact] + public void Should_Give_Correct_Result_For_FileLink_After_Roundtrip() + { + // Given + var fileLink1 = "https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"; + var fileLink2 = "https://github.com/myorg/myrepo/blob/develop/src/bar.cs#L23-L42"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .WithFileLink(new Uri(fileLink1)) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .WithFileLink(new Uri(fileLink2)) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().FileLink.ToString().ShouldBe(fileLink1); + result.Last().FileLink.ToString().ShouldBe(fileLink2); + } + [Fact] public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() { @@ -665,6 +938,35 @@ public void Should_Throw_If_FilePath_Is_Null() result.IsArgumentNullException("filePath"); } + [Fact] + public void Should_Give_Correct_Result_For_Identifier_After_Roundtrip() + { + // Given + var identifier = "identifier"; + var issue = + IssueBuilder + .NewIssue(identifier, "messageText", "providerType", "providerName") + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.Identifier.ShouldBe(identifier); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + [Fact] public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() { @@ -813,14 +1115,14 @@ public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() } [Fact] - public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() + public void Should_Give_Correct_Result_For_Run_After_Roundtrip() { // Given - var projectFileRelativePath = @"src/myproj.file"; + var run = "run"; var issue = IssueBuilder .NewIssue("message", "providerType", "providerName") - .InProjectFile(projectFileRelativePath) + .ForRun(run) .Create(); var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); @@ -831,7 +1133,7 @@ public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtr var result = filePath.DeserializeToIssue(); // Then - result.ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath); + result.Run.ShouldBe(run); } finally { @@ -843,14 +1145,14 @@ public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtr } [Fact] - public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() + public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() { // Given - var projectName = "projectName"; + var projectFileRelativePath = @"src/myproj.file"; var issue = IssueBuilder .NewIssue("message", "providerType", "providerName") - .InProjectOfName(projectName) + .InProjectFile(projectFileRelativePath) .Create(); var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); @@ -861,7 +1163,7 @@ public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() var result = filePath.DeserializeToIssue(); // Then - result.ProjectName.ShouldBe(projectName); + result.ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath); } finally { @@ -873,14 +1175,14 @@ public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() } [Fact] - public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() + public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() { // Given - var affectedFileRelativePath = @"src/foo.bar"; + var projectName = "projectName"; var issue = IssueBuilder .NewIssue("message", "providerType", "providerName") - .InFile(affectedFileRelativePath) + .InProjectOfName(projectName) .Create(); var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); @@ -891,7 +1193,7 @@ public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundt var result = filePath.DeserializeToIssue(); // Then - result.AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath); + result.ProjectName.ShouldBe(projectName); } finally { @@ -903,10 +1205,40 @@ public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundt } [Fact] - public void Should_Give_Correct_Result_For_Line_After_Roundtrip() + public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() { // Given - var line = 42; + var affectedFileRelativePath = @"src/foo.bar"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(affectedFileRelativePath) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Line_After_Roundtrip() + { + // Given + var line = 42; var issue = IssueBuilder .NewIssue("message", "providerType", "providerName") @@ -932,6 +1264,126 @@ public void Should_Give_Correct_Result_For_Line_After_Roundtrip() } } + [Fact] + public void Should_Give_Correct_Result_For_EndLine_After_Roundtrip() + { + // Given + var endLine = 420; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", 42, endLine, null, null) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.EndLine.ShouldBe(endLine); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Column_After_Roundtrip() + { + // Given + var column = 23; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", 42, column) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.Column.ShouldBe(column); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_EndColumn_After_Roundtrip() + { + // Given + var endColumn = 230; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", 42, 50, 1, endColumn) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.EndColumn.ShouldBe(endColumn); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_FileLink_After_Roundtrip() + { + // Given + var fileLink = "https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithFileLink(new Uri(fileLink)) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.FileLink.ToString().ShouldBe(fileLink); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + [Fact] public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() { @@ -1083,6 +1535,44 @@ public void Should_Throw_If_FilePath_Is_Null() result.IsArgumentNullException("filePath"); } + [Fact] + public void Should_Give_Correct_Result_For_Identifier_After_Roundtrip() + { + // Given + var identifier1 = "identifier1"; + var identifier2 = "identifier2"; + var issues = + new List + { + IssueBuilder + .NewIssue(identifier1, "messageText1", "providerType1", "providerName1") + .Create(), + IssueBuilder + .NewIssue(identifier2, "messageText2", "providerType2", "providerName2") + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Identifier.ShouldBe(identifier1); + result.Last().Identifier.ShouldBe(identifier2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + [Fact] public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() { @@ -1277,6 +1767,46 @@ public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() } } + [Fact] + public void Should_Give_Correct_Result_For_Run_After_Roundtrip() + { + // Given + var run1 = "run1"; + var run2 = "run2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .ForRun(run1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .ForRun(run2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Run.ShouldBe(run1); + result.Last().Run.ShouldBe(run2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + [Fact] public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() { @@ -1437,6 +1967,166 @@ public void Should_Give_Correct_Result_For_Line_After_Roundtrip() } } + [Fact] + public void Should_Give_Correct_Result_For_EndLine_After_Roundtrip() + { + // Given + var endLine1 = 230; + var endLine2 = 420; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", 23, endLine1, null, null) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", 42, endLine2, null, null) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().EndLine.ShouldBe(endLine1); + result.Last().EndLine.ShouldBe(endLine2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Column_After_Roundtrip() + { + // Given + var column1 = 23; + var column2 = 42; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", 123, column1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", 123, column2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Column.ShouldBe(column1); + result.Last().Column.ShouldBe(column2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_EndColumn_After_Roundtrip() + { + // Given + var endColumn1 = 23; + var endColumn2 = 42; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", 5, 50, 1, endColumn1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", 5, 50, 1, endColumn2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().EndColumn.ShouldBe(endColumn1); + result.Last().EndColumn.ShouldBe(endColumn2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_FileLink_After_Roundtrip() + { + // Given + var fileLink1 = "https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"; + var fileLink2 = "https://github.com/myorg/myrepo/blob/develop/src/bar.cs#L23-L42"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .WithFileLink(new Uri(fileLink1)) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .WithFileLink(new Uri(fileLink2)) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().FileLink.ToString().ShouldBe(fileLink1); + result.Last().FileLink.ToString().ShouldBe(fileLink2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + [Fact] public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() { diff --git a/src/Cake.Issues.Tests/Serialization/SerializableIssueV3ExtensionsTests.cs b/src/Cake.Issues.Tests/Serialization/SerializableIssueV3ExtensionsTests.cs new file mode 100644 index 000000000..c223b55cb --- /dev/null +++ b/src/Cake.Issues.Tests/Serialization/SerializableIssueV3ExtensionsTests.cs @@ -0,0 +1,25 @@ +namespace Cake.Issues.Tests.Serialization +{ + using Cake.Issues.Serialization; + using Cake.Issues.Testing; + using Xunit; + + public sealed class SerializableIssueV3ExtensionsTests + { + public sealed class TheToIssueExtension + { + [Fact] + public void Should_Throw_If_SerializableIssue_Is_Null() + { + // Given + SerializableIssueV3 serializableIssue = null; + + // When + var result = Record.Exception(() => serializableIssue.ToIssue()); + + // Then + result.IsArgumentNullException("serializableIssue"); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Testfiles/issueV3.json b/src/Cake.Issues.Tests/Testfiles/issueV3.json new file mode 100644 index 000000000..94191f89a --- /dev/null +++ b/src/Cake.Issues.Tests/Testfiles/issueV3.json @@ -0,0 +1,21 @@ +{ + "Version": 3, + "Identifier": "Identifier", + "AffectedFileRelativePath": "src\/Foo\/Bar.cs", + "Line": 42, + "EndLine": 420, + "Column": 23, + "EndColumn": 230, + "MessageText": "Something went wrong.", + "MessageHtml": "Something went wrong.", + "MessageMarkdown": "Something went **wrong**.", + "Priority": 300, + "PriorityName": "Warning", + "ProjectFileRelativePath": "src\/Foo\/Bar.csproj", + "ProjectName": "Bar", + "ProviderName": "Test Provider", + "ProviderType": "TestProvider", + "Run": "TestRun", + "Rule": "Rule", + "RuleUrl": "https://google.com" +} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Testfiles/issuesV3.json b/src/Cake.Issues.Tests/Testfiles/issuesV3.json new file mode 100644 index 000000000..d3099e26e --- /dev/null +++ b/src/Cake.Issues.Tests/Testfiles/issuesV3.json @@ -0,0 +1,42 @@ +[ + { + "Version": 3, + "Identifier": "Identifier1", + "AffectedFileRelativePath": "src\/Foo\/Bar.cs", + "Line": 42, + "EndLine": 420, + "Column": 23, + "EndColumn": 230, + "MessageText": "Something went wrong.", + "MessageHtml": "Something went wrong.", + "MessageMarkdown": "Something went **wrong**.", + "Priority": 300, + "PriorityName": "Warning", + "ProjectFileRelativePath": "src\/Foo\/Bar.csproj", + "ProjectName": "Bar", + "ProviderName": "Test Provider", + "ProviderType": "TestProvider", + "Rule": "Rule", + "RuleUrl": "https://google.com" + }, + { + "Version": 3, + "Identifier": "Identifier2", + "AffectedFileRelativePath": "src\/Foo\/Bar2.cs", + "Line": null, + "EndLine": null, + "Column": null, + "EndColumn": null, + "MessageText": "Something went wrong again.", + "MessageHtml": "Something went wrong again.", + "MessageMarkdown": "Something went **wrong** again.", + "Priority": 300, + "PriorityName": "Warning", + "ProjectFileRelativePath": "src\/Foo\/Bar.csproj", + "ProjectName": "Bar", + "ProviderName": "Test Provider", + "ProviderType": "TestProvider", + "Rule": null, + "RuleUrl": null + } +] \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Testing/BaseConfigurableIssueProviderFixtureTests.cs b/src/Cake.Issues.Tests/Testing/BaseConfigurableIssueProviderFixtureTests.cs index 4d267d669..182507833 100644 --- a/src/Cake.Issues.Tests/Testing/BaseConfigurableIssueProviderFixtureTests.cs +++ b/src/Cake.Issues.Tests/Testing/BaseConfigurableIssueProviderFixtureTests.cs @@ -82,7 +82,7 @@ public void Should_Set_RepositorySettings() var result = new FakeConfigurableIssueProviderFixture("Build.log"); // Then - result.RepositorySettings.ShouldNotBeNull(); + result.ReadIssuesSettings.ShouldNotBeNull(); } [Fact] @@ -136,14 +136,14 @@ public void Should_Throw_If_RepositorySettings_Are_Null() // Given var fixture = new FakeConfigurableIssueProviderFixture("Build.log") { - RepositorySettings = null, + ReadIssuesSettings = null, }; // When var result = Record.Exception(() => fixture.ReadIssues()); // Then - result.IsInvalidOperationException("No repository settings set."); + result.IsInvalidOperationException("No settings for reading issues set."); } [Fact] diff --git a/src/Cake.Issues.Tests/Testing/BaseIssueProviderFixtureTests.cs b/src/Cake.Issues.Tests/Testing/BaseIssueProviderFixtureTests.cs index 5cb0d3268..5aa2d628b 100644 --- a/src/Cake.Issues.Tests/Testing/BaseIssueProviderFixtureTests.cs +++ b/src/Cake.Issues.Tests/Testing/BaseIssueProviderFixtureTests.cs @@ -31,7 +31,7 @@ public void Should_Set_RepositorySettings() var result = new FakeIssueProviderFixture(); // Then - result.RepositorySettings.ShouldNotBeNull(); + result.ReadIssuesSettings.ShouldNotBeNull(); } } @@ -59,14 +59,14 @@ public void Should_Throw_If_RepositorySettings_Are_Null() // Given var fixture = new FakeIssueProviderFixture { - RepositorySettings = null, + ReadIssuesSettings = null, }; // When var result = Record.Exception(() => fixture.ReadIssues()); // Then - result.IsInvalidOperationException("No repository settings set."); + result.IsInvalidOperationException("No settings for reading issues set."); } [Fact] diff --git a/src/Cake.Issues.Tests/Testing/BaseMultiFormatIssueProviderFixtureTests.cs b/src/Cake.Issues.Tests/Testing/BaseMultiFormatIssueProviderFixtureTests.cs index 540dcf176..be71cbfff 100644 --- a/src/Cake.Issues.Tests/Testing/BaseMultiFormatIssueProviderFixtureTests.cs +++ b/src/Cake.Issues.Tests/Testing/BaseMultiFormatIssueProviderFixtureTests.cs @@ -82,7 +82,7 @@ public void Should_Set_RepositorySettings() var result = new FakeMultiFormatIssueProviderFixture("Build.log"); // Then - result.RepositorySettings.ShouldNotBeNull(); + result.ReadIssuesSettings.ShouldNotBeNull(); } [Fact] @@ -136,14 +136,14 @@ public void Should_Throw_If_RepositorySettings_Are_Null() // Given var fixture = new FakeMultiFormatIssueProviderFixture("Build.log") { - RepositorySettings = null, + ReadIssuesSettings = null, }; // When var result = Record.Exception(() => fixture.ReadIssues()); // Then - result.IsInvalidOperationException("No repository settings set."); + result.IsInvalidOperationException("No settings for reading issues set."); } [Fact] diff --git a/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs b/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs index 918571a1d..09ba57b92 100644 --- a/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs +++ b/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs @@ -5,19 +5,25 @@ internal class IssueCheckerFixture : IssueBuilderFixture { public IssueCheckerFixture() - : this("Message", "ProviderType", "ProviderName") + : this("Identifier", "Message", "ProviderType", "ProviderName") { } - public IssueCheckerFixture(string messageText, string providerType, string providerName) - : base(messageText, providerType, providerName) + public IssueCheckerFixture(string identifier, string messageText, string providerType, string providerName) + : base(identifier, messageText, providerType, providerName) { this.ProviderType = providerType; this.ProviderName = providerName; + this.Run = "Test Run"; + this.Identifier = identifier; this.ProjectFileRelativePath = @"src\project.file"; this.ProjectName = "ProjectName"; this.AffectedFileRelativePath = @"src\source.file"; this.Line = 42; + this.EndLine = 420; + this.Column = 23; + this.EndColumn = 230; + this.FileLink = new Uri("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12"); this.MessageText = messageText; this.MessageHtml = "messageHtml"; this.MessageMarkdown = "messageMarkdown"; @@ -27,10 +33,12 @@ public IssueCheckerFixture(string messageText, string providerType, string provi this.RuleUrl = new Uri("https://google.com"); this.IssueBuilder + .ForRun(this.Run) .WithMessageInHtmlFormat(this.MessageHtml) .WithMessageInMarkdownFormat(this.MessageMarkdown) .InProject(this.ProjectFileRelativePath, this.ProjectName) - .InFile(this.AffectedFileRelativePath, this.Line) + .InFile(this.AffectedFileRelativePath, this.Line, this.EndLine, this.Column, this.EndColumn) + .WithFileLink(this.FileLink) .OfRule(this.Rule, this.RuleUrl) .WithPriority(this.Priority, this.PriorityName); @@ -44,6 +52,10 @@ public IssueCheckerFixture(string messageText, string providerType, string provi public string ProviderName { get; private set; } + public string Run { get; private set; } + + public string Identifier { get; private set; } + public string ProjectFileRelativePath { get; private set; } public string ProjectName { get; private set; } @@ -52,6 +64,14 @@ public IssueCheckerFixture(string messageText, string providerType, string provi public int Line { get; private set; } + public int EndLine { get; private set; } + + public int Column { get; private set; } + + public int EndColumn { get; private set; } + + public Uri FileLink { get; private set; } + public string MessageText { get; private set; } public string MessageHtml { get; private set; } diff --git a/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs b/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs index 2b8c8fddf..d593d31eb 100644 --- a/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs +++ b/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs @@ -144,10 +144,16 @@ public void Should_Throw_If_Issue_Is_Null() issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -171,10 +177,16 @@ public void Should_Not_Throw_If_All_Values_Are_The_Same() fixture.Issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -194,7 +206,7 @@ public void Should_Not_Throw_If_All_Values_Are_The_Same() public void Should_Throw_If_ProviderType_Is_Different(string expectedValue, string actualValue) { // Given - var fixture = new IssueCheckerFixture("Message", actualValue, "ProviderName"); + var fixture = new IssueCheckerFixture("Identifier", "Message", actualValue, "ProviderName"); // When var result = Record.Exception(() => @@ -202,10 +214,16 @@ public void Should_Throw_If_ProviderType_Is_Different(string expectedValue, stri fixture.Issue, expectedValue, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -227,7 +245,7 @@ public void Should_Throw_If_ProviderType_Is_Different(string expectedValue, stri public void Should_Throw_If_ProviderName_Is_Different(string expectedValue, string actualValue) { // Given - var fixture = new IssueCheckerFixture("Message", "ProviderType", actualValue); + var fixture = new IssueCheckerFixture("Identifier", "Message", "ProviderType", actualValue); // When var result = Record.Exception(() => @@ -235,10 +253,16 @@ public void Should_Throw_If_ProviderName_Is_Different(string expectedValue, stri fixture.Issue, fixture.ProviderType, expectedValue, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -252,6 +276,88 @@ public void Should_Throw_If_ProviderName_Is_Different(string expectedValue, stri result.Message.ShouldStartWith("Expected issue.ProviderName"); } + [Theory] + [InlineData("Run", "Foo")] + [InlineData(null, "Foo")] + [InlineData("", "Foo")] + [InlineData(" ", "Foo")] + public void Should_Throw_If_Run_Is_Different(string expectedValue, string actualValue) + { + // Given + var fixture = new IssueCheckerFixture(); + var issue = + fixture.IssueBuilder + .ForRun(actualValue) + .Create(); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + fixture.Issue, + fixture.ProviderType, + fixture.ProviderName, + expectedValue, + fixture.Identifier, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.Run"); + } + + [Theory] + [InlineData("Message", "Foo")] + [InlineData(null, "Foo")] + [InlineData("", "Foo")] + [InlineData(" ", "Foo")] + public void Should_Throw_If_Identifier_Is_Different(string expectedValue, string actualValue) + { + // Given + var fixture = new IssueCheckerFixture(actualValue, "Message", "ProviderType", "ProviderName"); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + fixture.Issue, + fixture.ProviderType, + fixture.ProviderName, + fixture.Run, + expectedValue, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.Identifier"); + } + [Theory] [InlineData(@"src\project.file", @"src\foo")] public void Should_Throw_If_ProjectFileRelativePath_Is_Different(string expectedValue, string actualValue) @@ -269,10 +375,16 @@ public void Should_Throw_If_ProjectFileRelativePath_Is_Different(string expected issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, expectedValue, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -306,10 +418,16 @@ public void Should_Throw_If_ProjectName_Is_Different(string expectedValue, strin issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, expectedValue, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -340,10 +458,16 @@ public void Should_Throw_If_AffectedFileRelativePath_Is_Different(string expecte issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, expectedValue, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -376,10 +500,16 @@ public void Should_Throw_If_Line_Is_Different(int? expectedValue, int? actualVal issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, expectedValue, + null, + null, + null, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -393,6 +523,173 @@ public void Should_Throw_If_Line_Is_Different(int? expectedValue, int? actualVal result.Message.ShouldStartWith("Expected issue.Line"); } + [Theory] + [InlineData(420, 230)] + [InlineData(null, 420)] + [InlineData(420, null)] + public void Should_Throw_If_EndLine_Is_Different(int? expectedValue, int? actualValue) + { + // Given + var fixture = new IssueCheckerFixture(); + var issue = + fixture.IssueBuilder + .InFile(fixture.AffectedFileRelativePath, fixture.Line, actualValue, fixture.Column, fixture.EndColumn) + .Create(); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + issue, + fixture.ProviderType, + fixture.ProviderName, + fixture.Run, + fixture.Identifier, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + expectedValue, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.EndLine"); + } + + [Theory] + [InlineData(42, 23)] + [InlineData(null, 42)] + [InlineData(42, null)] + public void Should_Throw_If_Column_Is_Different(int? expectedValue, int? actualValue) + { + // Given + var fixture = new IssueCheckerFixture(); + var issue = + fixture.IssueBuilder + .InFile(fixture.AffectedFileRelativePath, fixture.Line, fixture.EndLine, actualValue, null) + .Create(); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + issue, + fixture.ProviderType, + fixture.ProviderName, + fixture.Run, + fixture.Identifier, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + fixture.EndLine, + expectedValue, + null, + fixture.FileLink, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.Column"); + } + + [Theory] + [InlineData(42, 23)] + [InlineData(null, 42)] + [InlineData(42, null)] + public void Should_Throw_If_EndColumn_Is_Different(int? expectedValue, int? actualValue) + { + // Given + var fixture = new IssueCheckerFixture(); + var issue = + fixture.IssueBuilder + .InFile(fixture.AffectedFileRelativePath, fixture.Line, fixture.EndLine, fixture.Column, actualValue) + .Create(); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + issue, + fixture.ProviderType, + fixture.ProviderName, + fixture.Run, + fixture.Identifier, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + fixture.EndLine, + fixture.Column, + expectedValue, + fixture.FileLink, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.EndColumn"); + } + + [Theory] + [InlineData("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12", "https://github.com/foo/bar/blob/develop/src/bar.cs")] + [InlineData("https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L12", "https://github.com/myorg/myrepo/blob/develop/src/foo.cs#L10-L13")] + public void Should_Throw_If_FileLink_Is_Different(string expectedValue, string actualValue) + { + // Given + var fixture = new IssueCheckerFixture(); + var issue = + fixture.IssueBuilder + .WithFileLink(new Uri(actualValue)) + .Create(); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + issue, + fixture.ProviderType, + fixture.ProviderName, + fixture.Run, + fixture.Identifier, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + new Uri(expectedValue), + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.FileLink"); + } + [Theory] [InlineData("Message", "Foo")] [InlineData(null, "Foo")] @@ -401,7 +698,7 @@ public void Should_Throw_If_Line_Is_Different(int? expectedValue, int? actualVal public void Should_Throw_If_MessageText_Is_Different(string expectedValue, string actualValue) { // Given - var fixture = new IssueCheckerFixture(actualValue, "ProviderType", "ProviderName"); + var fixture = new IssueCheckerFixture("Identifier", actualValue, "ProviderType", "ProviderName"); // When var result = Record.Exception(() => @@ -409,10 +706,16 @@ public void Should_Throw_If_MessageText_Is_Different(string expectedValue, strin fixture.Issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, expectedValue, fixture.MessageHtml, fixture.MessageMarkdown, @@ -446,10 +749,16 @@ public void Should_Throw_If_MessageHtml_Is_Different(string expectedValue, strin fixture.Issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, expectedValue, fixture.MessageMarkdown, @@ -483,10 +792,16 @@ public void Should_Throw_If_MessageMarkdown_Is_Different(string expectedValue, s fixture.Issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, expectedValue, @@ -517,10 +832,16 @@ public void Should_Throw_If_Priority_Is_Different(IssuePriority expectedValue, I issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -554,10 +875,16 @@ public void Should_Throw_If_PriorityName_Is_Different(string expectedValue, stri issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -591,10 +918,16 @@ public void Should_Throw_If_Rule_Is_Different(string expectedValue, string actua issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, @@ -625,10 +958,16 @@ public void Should_Throw_If_RuleUrl_Is_Different(string expectedValue, string ac issue, fixture.ProviderType, fixture.ProviderName, + fixture.Run, + fixture.Identifier, fixture.ProjectFileRelativePath, fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, + fixture.EndLine, + fixture.Column, + fixture.EndColumn, + fixture.FileLink, fixture.MessageText, fixture.MessageHtml, fixture.MessageMarkdown, diff --git a/src/Cake.Issues.Tests/UriExtensionsTests.cs b/src/Cake.Issues.Tests/UriExtensionsTests.cs new file mode 100644 index 000000000..6907df88e --- /dev/null +++ b/src/Cake.Issues.Tests/UriExtensionsTests.cs @@ -0,0 +1,51 @@ +namespace Cake.Issues.Tests +{ + using System; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class UriExtensionsTests + { + public sealed class TheAppendMethod + { + [Fact] + public void Should_Throw_If_Uri_Is_Null() + { + // Given + Uri uri = null; + var path = "foo"; + + // When + var result = Record.Exception(() => + uri.Append(path)); + + // Then + result.IsArgumentNullException("uri"); + } + + [Theory] + [InlineData("https://google.com/foo/bar", "https://google.com", "foo", "bar")] + [InlineData("https://google.com/foo/bar", "https://google.com/", "foo", "bar")] + [InlineData("https://google.com/foo/bar", "https://google.com", "/foo", "bar")] + [InlineData("https://google.com/foo/bar", "https://google.com", "foo/", "bar")] + [InlineData("https://google.com/foo/bar", "https://google.com", "foo", "/bar")] + [InlineData("https://google.com/foo/bar/", "https://google.com", "foo", "bar/")] + [InlineData("https://google.com/foo/bar", "https://google.com", "foo/", "/bar")] + [InlineData("https://google.com/bar", "https://google.com", null, "bar")] + [InlineData("https://google.com/bar", "https://google.com", "", "bar")] + [InlineData("https://google.com/bar", "https://google.com", " ", "bar")] + [InlineData("https://google.com/bar", "https://google.com", "/", "/bar")] + public void Should_Append_Paths(string expectedPath, string uri, params string[] paths) + { + // Given + + // When + var result = new Uri(uri).Append(paths); + + // Then + result.ToString().ShouldBe(expectedPath); + } + } + } +} diff --git a/src/Cake.Issues/Aliases.FileLinking.cs b/src/Cake.Issues/Aliases.FileLinking.cs new file mode 100644 index 000000000..ddb70f4f0 --- /dev/null +++ b/src/Cake.Issues/Aliases.FileLinking.cs @@ -0,0 +1,294 @@ +namespace Cake.Issues +{ + using System; + using Cake.Core; + using Cake.Core.Annotations; + + /// + /// Contains functionality related to linking to files. + /// + public static partial class Aliases + { + /// + /// Gets an instance of the file link settings for linking to files + /// based on a custom pattern. + /// + /// The context. + /// Pattern of the file link. + /// See + /// for a list of tokens supported in the pattern. + /// Settings for linking files. + /// + /// Creates file link settings to an internal source hosting site: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettings( + this ICakeContext context, + string pattern) + { + context.NotNull(nameof(context)); + pattern.NotNullOrWhiteSpace(nameof(pattern)); + + return FileLinkSettings.ForPattern(pattern); + } + + /// + /// Gets an instance of the file link settings for linking to files + /// based on a custom action. + /// + /// The context. + /// Callback called for building the file link. + /// File link settings. + /// Settings for linking files. + /// + /// Creates file link settings to an internal source hosting site with + /// parameters set dynamically, based on values of the issue: + /// + /// + /// { + /// var result = + /// new Uri("https://awesomesource/") + /// .Append(issue.FilePath()); + /// + /// if (issue.Line.HasValue) + /// { + /// result = result.Append("?line={issue.Line.Value}") + /// } + /// + /// return result; + /// }); + /// ]]> + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettings( + this ICakeContext context, + Func builder) + { + context.NotNull(nameof(context)); + builder.NotNull(nameof(builder)); + + return FileLinkSettings.ForAction(builder); + } + + /// + /// Gets an instance of the file link settings for linking files hosted on GitHub on a specific branch. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://github.com/cake-contrib/Cake.Issues.Reporting.Generic. + /// Name of the branch on which the file linking will be based on. + /// Settings for linking to files hosted in GitHub. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForGitHubBranch( + this ICakeContext context, + Uri repositoryUrl, + string branch) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + branch.NotNullOrWhiteSpace(nameof(branch)); + + return context.IssueFileLinkSettingsForGitHubBranch(repositoryUrl, branch, null); + } + + /// + /// Gets an instance of the file link settings for linking files hosted on GitHub + /// in a sub-folder on a specific branch. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://github.com/cake-contrib/Cake.Issues.Reporting.Generic. + /// Name of the branch on which the file linking will be based on. + /// Root path of the files. + /// null if files are in the root of the repository. + /// Settings for linking to files hosted in GitHub. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForGitHubBranch( + this ICakeContext context, + Uri repositoryUrl, + string branch, + string rootPath) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + branch.NotNullOrWhiteSpace(nameof(branch)); + + return + FileLinkSettings + .ForGitHub(repositoryUrl) + .Branch(branch) + .WithRootPath(rootPath); + } + + /// + /// Gets an instance of the file link settings for linking files hosted on GitHub fo a specific commit. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://github.com/cake-contrib/Cake.Issues.Reporting.Generic. + /// The commit id on which the file linking will be based on. + /// Settings for linking to files hosted in GitHub. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForGitHubCommit( + this ICakeContext context, + Uri repositoryUrl, + string commitId) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + commitId.NotNullOrWhiteSpace(nameof(commitId)); + + return context.IssueFileLinkSettingsForGitHubCommit(repositoryUrl, commitId, null); + } + + /// + /// Gets an instance of the file link settings for linking files hosted on GitHub + /// in a sub-folder for a specific commit. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://github.com/cake-contrib/Cake.Issues.Reporting.Generic. + /// The commit id on which the file linking will be based on. + /// Root path of the files. + /// null or if files are in the root of the repository. + /// Settings for linking to files hosted in GitHub. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForGitHubCommit( + this ICakeContext context, + Uri repositoryUrl, + string commitId, + string rootPath) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + commitId.NotNullOrWhiteSpace(nameof(commitId)); + + return + FileLinkSettings + .ForGitHub(repositoryUrl) + .Commit(commitId) + .WithRootPath(rootPath); + } + + /// + /// Gets an instance of the file link settings for linking to files hosted in Azure DevOps + /// on a specific branch. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://dev.azure.com/myorganization/_git/myrepo. + /// Name of the branch on which the file linking will be based on. + /// Settings for linking files hosted on Azure DevOps or Azure DevOps Server. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForAzureDevOpsBranch( + this ICakeContext context, + Uri repositoryUrl, + string branch) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + branch.NotNullOrWhiteSpace(nameof(branch)); + + return context.IssueFileLinkSettingsForAzureDevOpsBranch(repositoryUrl, branch, null); + } + + /// + /// Gets an instance of the file link settings for linking to files hosted in Azure DevOps + /// in a sub-folder on a specific branch. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://dev.azure.com/myorganization/_git/myrepo. + /// Name of the branch on which the file linking will be based on. + /// Root path of the files. + /// null if files are in the root of the repository. + /// Settings for linking files hosted on Azure DevOps. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForAzureDevOpsBranch( + this ICakeContext context, + Uri repositoryUrl, + string branch, + string rootPath) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + branch.NotNullOrWhiteSpace(nameof(branch)); + + return + FileLinkSettings + .ForAzureDevOps(repositoryUrl) + .Branch(branch) + .WithRootPath(rootPath); + } + + /// + /// Gets an instance of the file link settings for linking to files hosted in Azure DevOps + /// for a specific commit. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://dev.azure.com/myorganization/_git/myrepo. + /// The commit id on which the file linking will be based on. + /// Settings for linking files hosted on Azure DevOps or Azure DevOps Server. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForAzureDevOpsCommit( + this ICakeContext context, + Uri repositoryUrl, + string commitId) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + commitId.NotNullOrWhiteSpace(nameof(commitId)); + + return context.IssueFileLinkSettingsForAzureDevOpsCommit(repositoryUrl, commitId, null); + } + + /// + /// Gets an instance of the file link settings for linking to files hosted in Azure DevOps + /// in a sub-folder for a specific commit. + /// + /// The context. + /// Full URL of the Git repository, + /// eg. https://dev.azure.com/myorganization/_git/myrepo. + /// The commit id on which the file linking will be based on. + /// Root path of the files. + /// null if files are in the root of the repository. + /// Settings for linking files hosted on Azure DevOps. + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.FileLinkingCakeAliasCategory)] + public static FileLinkSettings IssueFileLinkSettingsForAzureDevOpsCommit( + this ICakeContext context, + Uri repositoryUrl, + string commitId, + string rootPath) + { + context.NotNull(nameof(context)); + repositoryUrl.NotNull(nameof(repositoryUrl)); + commitId.NotNullOrWhiteSpace(nameof(commitId)); + + return + FileLinkSettings + .ForAzureDevOps(repositoryUrl) + .Commit(commitId) + .WithRootPath(rootPath); + } + } +} diff --git a/src/Cake.Issues/Aliases.NewIssue.cs b/src/Cake.Issues/Aliases.NewIssue.cs index a97ee1172..3e23ad827 100644 --- a/src/Cake.Issues/Aliases.NewIssue.cs +++ b/src/Cake.Issues/Aliases.NewIssue.cs @@ -9,7 +9,7 @@ public static partial class Aliases { /// - /// Initiates the creation of a new . + /// Initiates the creation of a new with as identifier. /// /// The context. /// The message of the issue. @@ -46,5 +46,54 @@ public static IssueBuilder NewIssue( return IssueBuilder.NewIssue(message, providerType, providerName); } + + /// + /// Initiates the creation of a new . + /// + /// The context. + /// The identifier of the issue. + /// The message of the issue. + /// The unique identifier of the issue provider. + /// The human friendly name of the issue provider. + /// Builder class for creating a new . + /// + /// Create a new warning for the myfile.txt file on line 42: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.CreateCakeAliasCategory)] + public static IssueBuilder NewIssue( + this ICakeContext context, + string identifier, + string message, + string providerType, + string providerName) + { +#pragma warning disable SA1123 // Do not place regions within elements + #region DupFinder Exclusion +#pragma warning restore SA1123 // Do not place regions within elements + + context.NotNull(nameof(context)); + identifier.NotNullOrWhiteSpace(nameof(identifier)); + message.NotNullOrWhiteSpace(nameof(message)); + providerType.NotNullOrWhiteSpace(nameof(providerType)); + providerName.NotNullOrWhiteSpace(nameof(providerName)); + + #endregion + + return IssueBuilder.NewIssue(identifier, message, providerType, providerName); + } } } diff --git a/src/Cake.Issues/Aliases.ReadIssues.cs b/src/Cake.Issues/Aliases.ReadIssues.cs index 1abb5a8d6..e961eeed2 100644 --- a/src/Cake.Issues/Aliases.ReadIssues.cs +++ b/src/Cake.Issues/Aliases.ReadIssues.cs @@ -99,13 +99,13 @@ public static IEnumerable ReadIssues( /// The settings. /// Issues reported by issue provider. /// - /// Read issues reported by JetBrains inspect code and format comments in Markdown: + /// Read issues reported by JetBrains inspect code and set run information: /// /// ReadIssues( public static IEnumerable ReadIssues( this ICakeContext context, IIssueProvider issueProvider, - ReadIssuesSettings settings) + IReadIssuesSettings settings) { context.NotNull(nameof(context)); issueProvider.NotNull(nameof(issueProvider)); @@ -142,13 +142,13 @@ public static IEnumerable ReadIssues( /// Issues reported by all issue providers. /// /// Read issues reported as MsBuild warnings and issues reported by JetBrains inspect code - /// with comments formatted as Markdown: + /// and set run information: /// /// ReadIssues( public static IEnumerable ReadIssues( this ICakeContext context, IEnumerable issueProviders, - ReadIssuesSettings settings) + IReadIssuesSettings settings) { context.NotNull(nameof(context)); settings.NotNull(nameof(settings)); diff --git a/src/Cake.Issues/BaseIssueComponent.cs b/src/Cake.Issues/BaseIssueComponent.cs index 049481873..e56c0f404 100644 --- a/src/Cake.Issues/BaseIssueComponent.cs +++ b/src/Cake.Issues/BaseIssueComponent.cs @@ -8,7 +8,7 @@ /// /// Type of settings. public abstract class BaseIssueComponent : IBaseIssueComponent - where T : RepositorySettings + where T : class, IRepositorySettings { /// /// Initializes a new instance of the class. diff --git a/src/Cake.Issues/BaseIssueProvider.cs b/src/Cake.Issues/BaseIssueProvider.cs index e3eeb928b..af26171bc 100644 --- a/src/Cake.Issues/BaseIssueProvider.cs +++ b/src/Cake.Issues/BaseIssueProvider.cs @@ -6,7 +6,7 @@ /// /// Base class for all issue provider implementations. /// - public abstract class BaseIssueProvider : BaseIssueComponent, IIssueProvider + public abstract class BaseIssueProvider : BaseIssueComponent, IIssueProvider { /// /// Initializes a new instance of the class. diff --git a/src/Cake.Issues/BaseLogFileFormat.cs b/src/Cake.Issues/BaseLogFileFormat.cs index facf44245..5c70409e2 100644 --- a/src/Cake.Issues/BaseLogFileFormat.cs +++ b/src/Cake.Issues/BaseLogFileFormat.cs @@ -31,7 +31,7 @@ protected BaseLogFileFormat(ICakeLog log) /// public abstract IEnumerable ReadIssues( TIssueProvider issueProvider, - RepositorySettings repositorySettings, + IRepositorySettings repositorySettings, TSettings issueProviderSettings); } } diff --git a/src/Cake.Issues/Cake.Issues.csproj b/src/Cake.Issues/Cake.Issues.csproj index bafe543eb..0bbfc481a 100644 --- a/src/Cake.Issues/Cake.Issues.csproj +++ b/src/Cake.Issues/Cake.Issues.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Cake.Issues/FileLinkSettings.cs b/src/Cake.Issues/FileLinkSettings.cs new file mode 100644 index 000000000..c1c54fcc3 --- /dev/null +++ b/src/Cake.Issues/FileLinkSettings.cs @@ -0,0 +1,95 @@ +namespace Cake.Issues +{ + using System; + using System.Collections.Generic; + using Cake.Issues.FileLinking; + + /// + /// Settings how issues should be linked to files. + /// + public class FileLinkSettings + { + private readonly Func, Uri> builder; + + /// + /// Initializes a new instance of the class. + /// + /// Callback called for building the file link. + internal FileLinkSettings(Func, Uri> builder) + { + builder.NotNull(nameof(builder)); + + this.builder = builder; + } + + /// + /// Returns settings to link files based on a custom pattern. + /// + /// Pattern of the file link. + /// See + /// for a list of tokens supported in the pattern. + /// File link settings. + public static FileLinkSettings ForPattern(string pattern) + { + pattern.NotNullOrWhiteSpace(nameof(pattern)); + + return + new FileLinkSettings( + (issue, values) => + { + return new Uri(pattern.ReplaceIssuePattern(issue)); + }); + } + + /// + /// Returns settings to link files based on a custom pattern. + /// + /// Callback called for building the file link. + /// File link settings. + public static FileLinkSettings ForAction(Func builder) + { + builder.NotNull(nameof(builder)); + + return new FileLinkSettings((issue, values) => builder(issue)); + } + + /// + /// Returns builder class for settings for linking to files hosted in GitHub. + /// + /// Full URL of the Git repository, + /// eg. https://github.com/cake-contrib/Cake.Issues. + /// Builder class for the settings. + public static GitHubFileLinkSettingsBuilder ForGitHub(Uri repositoryUrl) + { + repositoryUrl.NotNull(nameof(repositoryUrl)); + + return new GitHubFileLinkSettingsBuilder(repositoryUrl); + } + + /// + /// Returns builder class for settings for linking to files hosted in Azure DevOps. + /// + /// Full URL of the Git repository, + /// e.g. https://dev.azure.com/myorganization/_git/myrepo. + /// Builder class for the settings. + public static AzureDevOpsFileLinkSettingsBuilder ForAzureDevOps(Uri repositoryUrl) + { + repositoryUrl.NotNull(nameof(repositoryUrl)); + + return new AzureDevOpsFileLinkSettingsBuilder(repositoryUrl); + } + + /// + /// Returns the URL to the file on the source code hosting system + /// for the issue . + /// + /// Issue for which the link should be returned. + /// URL to the file on the source code hosting system. + public Uri GetFileLink(IIssue issue) + { + issue.NotNull(nameof(issue)); + + return this.builder(issue, new Dictionary()); + } + } +} diff --git a/src/Cake.Issues/FileLinking/AzureDevOpsFileLinkSettingsBuilder.cs b/src/Cake.Issues/FileLinking/AzureDevOpsFileLinkSettingsBuilder.cs new file mode 100644 index 000000000..1351a7f58 --- /dev/null +++ b/src/Cake.Issues/FileLinking/AzureDevOpsFileLinkSettingsBuilder.cs @@ -0,0 +1,97 @@ +namespace Cake.Issues.FileLinking +{ + using System; + using System.Collections.Generic; + + /// + /// Class for building settings for file links of files hosted on Azure DevOps. + /// + public class AzureDevOpsFileLinkSettingsBuilder + { + private readonly Uri repositoryUrl; + + /// + /// Initializes a new instance of the class. + /// + /// Full URL of the Git repository, + /// eg. https://github.com/cake-contrib/Cake.Issues. + internal AzureDevOpsFileLinkSettingsBuilder(Uri repositoryUrl) + { + repositoryUrl.NotNull(nameof(repositoryUrl)); + + this.repositoryUrl = repositoryUrl; + } + + /// + /// Returns settings to files on AzureDevOps on a specific branch. + /// + /// Name of the branch. + /// Class for setting additional settings. + public FileLinkOptionalSettingsBuilder Branch(string branchName) + { + branchName.NotNullOrWhiteSpace(nameof(branchName)); + + return + new FileLinkOptionalSettingsBuilder( + (issue, values) => + { + issue.NotNull(nameof(issue)); + values.NotNull(nameof(values)); + + var fileLinkPattern = + this.GetFileLinkPattern(issue, values, "GB", branchName); + return new Uri(fileLinkPattern.ReplaceIssuePattern(issue)); + }); + } + + /// + /// Returns settings to files on AzureDevOps on a specific commit. + /// + /// The commit id on which the file linking will be based on. + /// Class for setting additional settings. + public FileLinkOptionalSettingsBuilder Commit(string commitId) + { + commitId.NotNullOrWhiteSpace(nameof(commitId)); + + return + new FileLinkOptionalSettingsBuilder( + (issue, values) => + { + issue.NotNull(nameof(issue)); + values.NotNull(nameof(values)); + + var fileLinkPattern = + this.GetFileLinkPattern(issue, values, "GC", commitId); + return new Uri(fileLinkPattern.ReplaceIssuePattern(issue)); + }); + } + + private string GetFileLinkPattern( + IIssue issue, + IDictionary values, + string versionType, + string version) + { + issue.NotNull(nameof(issue)); + values.NotNull(nameof(values)); + versionType.NotNullOrWhiteSpace(nameof(versionType)); + version.NotNullOrWhiteSpace(nameof(version)); + + var result = + this.repositoryUrl.ToString().TrimEnd('/') + + "?path=/" + values.GetValueOrDefault("rootPath", null)?.TrimStart('/').WithEnding("/") + "{FilePath}" + + "&version=" + versionType + version; + + if (issue.Line.HasValue) + { + result += + "&line={Line}" + + "&lineEnd=" + (issue.EndLine.HasValue ? "{EndLine}" : "{Line}") + + "&lineStartColumn=" + (issue.Column.HasValue ? "{Column}" : "1") + + "&lineEndColumn=" + (issue.EndColumn.HasValue ? "{EndColumn}" : int.MaxValue.ToString()); + } + + return result; + } + } +} diff --git a/src/Cake.Issues/FileLinking/FileLinkOptionalSettingsBuilder.cs b/src/Cake.Issues/FileLinking/FileLinkOptionalSettingsBuilder.cs new file mode 100644 index 000000000..f8a776144 --- /dev/null +++ b/src/Cake.Issues/FileLinking/FileLinkOptionalSettingsBuilder.cs @@ -0,0 +1,53 @@ +namespace Cake.Issues.FileLinking +{ + using System; + using System.Collections.Generic; + + /// + /// Class containing builder for optional settings for linking to files. + /// + public class FileLinkOptionalSettingsBuilder : FileLinkSettings + { + private readonly Func, Uri> builder; + + /// + /// Initializes a new instance of the class. + /// + /// Callback called for building the file link. + internal FileLinkOptionalSettingsBuilder(Func, Uri> builder) + : base(builder) + { + builder.NotNull(nameof(builder)); + + this.builder = builder; + } + + /// + /// Sets the root path for the files. + /// + /// Root path for the files. + /// null if files are in the root of the repository. + /// Object for creating the file link. + public FileLinkSettings WithRootPath(string rootPath) + { + if (rootPath != null) + { + rootPath.NotNullOrWhiteSpace(nameof(rootPath)); + + if (!rootPath.IsValidPath()) + { + throw new ArgumentException($"Invalid path '{rootPath}'", nameof(rootPath)); + } + } + + return + new FileLinkSettings( + (issue, values) => + { + values.Add("rootPath", rootPath); + + return this.builder(issue, values); + }); + } + } +} diff --git a/src/Cake.Issues/FileLinking/GitHubFileLinkSettingsBuilder.cs b/src/Cake.Issues/FileLinking/GitHubFileLinkSettingsBuilder.cs new file mode 100644 index 000000000..c8a6f8ccc --- /dev/null +++ b/src/Cake.Issues/FileLinking/GitHubFileLinkSettingsBuilder.cs @@ -0,0 +1,102 @@ +namespace Cake.Issues.FileLinking +{ + using System; + + /// + /// Class for building settings for file links of files hosted on GitHub. + /// + public class GitHubFileLinkSettingsBuilder + { + private readonly Uri repositoryUrl; + + /// + /// Initializes a new instance of the class. + /// + /// Full URL of the Git repository, + /// eg. https://github.com/cake-contrib/Cake.Issues. + internal GitHubFileLinkSettingsBuilder(Uri repositoryUrl) + { + repositoryUrl.NotNull(nameof(repositoryUrl)); + + this.repositoryUrl = repositoryUrl; + } + + /// + /// Returns settings to files on GitHub on a specific branch. + /// + /// Name of the branch. + /// Class for setting additional settings. + public FileLinkOptionalSettingsBuilder Branch(string branchName) + { + branchName.NotNullOrWhiteSpace(nameof(branchName)); + + return + new FileLinkOptionalSettingsBuilder( + (issue, values) => + { + issue.NotNull(nameof(issue)); + values.NotNull(nameof(values)); + + var fileLinkPattern = + this.repositoryUrl.Append( + "blob", + branchName, + values.GetValueOrDefault("rootPath", null), + GetFilePathPattern(issue)).ToString(); + + return new Uri(fileLinkPattern.ReplaceIssuePattern(issue)); + }); + } + + /// + /// Returns settings to files on GitHub on a specific commit. + /// + /// The commit id on which the file linking will be based on. + /// Class for setting additional settings. + public FileLinkOptionalSettingsBuilder Commit(string commitId) + { + commitId.NotNullOrWhiteSpace(nameof(commitId)); + + return + new FileLinkOptionalSettingsBuilder( + (issue, values) => + { + issue.NotNull(nameof(issue)); + values.NotNull(nameof(values)); + + var fileLinkPattern = + this.repositoryUrl.Append( + "blob", + commitId, + values.GetValueOrDefault("rootPath", null), + GetFilePathPattern(issue)).ToString(); + + return new Uri(fileLinkPattern.ReplaceIssuePattern(issue)); + }); + } + + /// + /// Returns the pattern for the file path and line information. + /// + /// Issue for which the pattern should be returned. + /// Pattern. + private static string GetFilePathPattern(IIssue issue) + { + issue.NotNull(nameof(issue)); + + var filePathPattern = "{FilePath}"; + + if (issue.Line.HasValue) + { + filePathPattern += "#L{Line}"; + } + + if (issue.EndLine.HasValue) + { + filePathPattern += "-L{EndLine}"; + } + + return filePathPattern; + } + } +} diff --git a/src/Cake.Issues/FileLinking/IDictionaryExtensions.cs b/src/Cake.Issues/FileLinking/IDictionaryExtensions.cs new file mode 100644 index 000000000..bc08a5638 --- /dev/null +++ b/src/Cake.Issues/FileLinking/IDictionaryExtensions.cs @@ -0,0 +1,29 @@ +namespace Cake.Issues.FileLinking +{ + using System.Collections.Generic; + + /// + /// Extensions for . + /// + internal static class IDictionaryExtensions + { + /// + /// Gets the value associated with a key or the default value. + /// + /// Type of the key in the dictionary. + /// Type of the value in the dictionary. + /// Dictionary to read the key from. + /// The key whose value to get. + /// Value to return if key does not exist. + /// The value associated with the key if it exists or . + public static TValue GetValueOrDefault( + this IDictionary dictionary, + TKey key, + TValue defaultValue) + { + dictionary.NotNull(nameof(dictionary)); + + return dictionary.TryGetValue(key, out var value) ? value : defaultValue; + } + } +} diff --git a/src/Cake.Issues/IBaseIssueComponent.cs b/src/Cake.Issues/IBaseIssueComponent.cs index 8d10ddc91..435418dac 100644 --- a/src/Cake.Issues/IBaseIssueComponent.cs +++ b/src/Cake.Issues/IBaseIssueComponent.cs @@ -7,7 +7,7 @@ /// /// Type of settings. public interface IBaseIssueComponent - where T : RepositorySettings + where T : IRepositorySettings { /// /// Initializes the component. diff --git a/src/Cake.Issues/IIssue.cs b/src/Cake.Issues/IIssue.cs index 0a90ff793..a0a32e262 100644 --- a/src/Cake.Issues/IIssue.cs +++ b/src/Cake.Issues/IIssue.cs @@ -8,6 +8,12 @@ /// public interface IIssue { + /// + /// Gets the identifier for the message. + /// The identifier can be used to identify the same issue across multiple runs. + /// + string Identifier { get; } + /// /// Gets the path to the project to which the file affected by the issue belongs. /// The path is relative to the repository root. @@ -34,6 +40,30 @@ public interface IIssue /// int? Line { get; } + /// + /// Gets the end of the line range in the file where the issues has occurred. + /// null if the issue affects the whole file, an asssembly or only a single line. + /// + int? EndLine { get; } + + /// + /// Gets the column in the file where the issues has occurred. + /// null if the issue affects the whole file or an asssembly. + /// + int? Column { get; } + + /// + /// Gets the end of the column range in the file where the issues has occurred. + /// null if the issue affects the whole file, an asssembly or only a single column. + /// + int? EndColumn { get; } + + /// + /// Gets or sets a link to the position in the file where the issue ocurred. + /// null if was not set while reading issue. + /// + Uri FileLink { get; set; } + /// /// Gets the message of the issue in text format. /// @@ -73,6 +103,12 @@ public interface IIssue /// Uri RuleUrl { get; } + /// + /// Gets or sets the description of the run. + /// Can be null or if no run information is provided. + /// + string Run { get; set; } + /// /// Gets the type of the issue provider. /// diff --git a/src/Cake.Issues/IIssueComparer.cs b/src/Cake.Issues/IIssueComparer.cs index d7367fba3..dfb3e251e 100644 --- a/src/Cake.Issues/IIssueComparer.cs +++ b/src/Cake.Issues/IIssueComparer.cs @@ -38,6 +38,18 @@ public IIssueComparer() /// /// /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// /// /// public IIssueComparer(bool compareOnlyPersistentProperties) @@ -59,10 +71,15 @@ public bool Equals(IIssue x, IIssue y) } return + (x.Identifier == y.Identifier) && (this.compareOnlyPersistentProperties || x.ProjectFileRelativePath?.FullPath == y.ProjectFileRelativePath?.FullPath) && (x.ProjectName == y.ProjectName) && (this.compareOnlyPersistentProperties || x.AffectedFileRelativePath?.FullPath == y.AffectedFileRelativePath?.FullPath) && (this.compareOnlyPersistentProperties || x.Line == y.Line) && + (this.compareOnlyPersistentProperties || x.EndLine == y.EndLine) && + (this.compareOnlyPersistentProperties || x.Column == y.Column) && + (this.compareOnlyPersistentProperties || x.EndColumn == y.EndColumn) && + (this.compareOnlyPersistentProperties || x.FileLink == y.FileLink) && (x.MessageText == y.MessageText) && (x.MessageHtml == y.MessageHtml) && (x.MessageMarkdown == y.MessageMarkdown) && @@ -70,6 +87,7 @@ public bool Equals(IIssue x, IIssue y) (x.PriorityName == y.PriorityName) && (x.Rule == y.Rule) && (x.RuleUrl?.ToString() == y.RuleUrl?.ToString()) && + (x.Run == y.Run) && (x.ProviderType == y.ProviderType) && (x.ProviderName == y.ProviderName); } @@ -86,6 +104,7 @@ public int GetHashCode(IIssue obj) { return GetHashCode( + obj.Identifier, obj.ProjectName, obj.MessageText, obj.MessageHtml, @@ -94,6 +113,7 @@ public int GetHashCode(IIssue obj) obj.PriorityName, obj.Rule, obj.RuleUrl, + obj.Run, obj.ProviderType, obj.ProviderName); } @@ -101,10 +121,15 @@ public int GetHashCode(IIssue obj) { return GetHashCode( + obj.Identifier, obj.ProjectFileRelativePath?.ToString(), obj.ProjectName, obj.AffectedFileRelativePath?.ToString(), obj.Line, + obj.EndLine, + obj.Column, + obj.EndColumn, + obj.FileLink, obj.MessageText, obj.MessageHtml, obj.MessageMarkdown, @@ -112,6 +137,7 @@ public int GetHashCode(IIssue obj) obj.PriorityName, obj.Rule, obj.RuleUrl, + obj.Run, obj.ProviderType, obj.ProviderName); } diff --git a/src/Cake.Issues/IIssueExtensions.cs b/src/Cake.Issues/IIssueExtensions.cs index 4c3c604bc..efd1710b5 100644 --- a/src/Cake.Issues/IIssueExtensions.cs +++ b/src/Cake.Issues/IIssueExtensions.cs @@ -7,6 +7,53 @@ /// public static class IIssueExtensions { + /// + /// Returns the line and column range in the format {Line}:{Column}-{EndLine}:{EndColumn}. + /// + /// Issue for which the line and column range should be returned. + /// Line and column range. + public static string LineRange(this IIssue issue) + { + issue.NotNull(nameof(issue)); + + return issue.LineRange(true); + } + + /// + /// Returns the line range in the format {Line}:{Column}-{EndLine}:{EndColumn}. + /// + /// Issue for which the line range should be returned. + /// Flag if column information should also be returned. + /// Line and column range. + public static string LineRange(this IIssue issue, bool addColumn) + { + issue.NotNull(nameof(issue)); + + string result = string.Empty; + + if (issue.Line.HasValue) + { + result += issue.Line.ToString(); + + if (addColumn && issue.Column.HasValue) + { + result += $":{issue.Column.Value}"; + } + + if (issue.EndLine.HasValue) + { + result += $"-{issue.EndLine.Value}"; + + if (addColumn && issue.EndColumn.HasValue) + { + result += $":{issue.EndColumn.Value}"; + } + } + } + + return result; + } + /// /// Gets the message of the issue in a specific format. /// If the message is not available in the specific format, the message in @@ -20,17 +67,13 @@ public static string Message(this IIssue issue, IssueCommentFormat format) { issue.NotNull(nameof(issue)); - switch (format) + return format switch { - case IssueCommentFormat.PlainText: - return issue.MessageText; - case IssueCommentFormat.Html: - return !string.IsNullOrEmpty(issue.MessageHtml) ? issue.MessageHtml : issue.MessageText; - case IssueCommentFormat.Markdown: - return !string.IsNullOrEmpty(issue.MessageMarkdown) ? issue.MessageMarkdown : issue.MessageText; - default: - throw new ArgumentOutOfRangeException(nameof(format)); - } + IssueCommentFormat.PlainText => issue.MessageText, + IssueCommentFormat.Html => !string.IsNullOrEmpty(issue.MessageHtml) ? issue.MessageHtml : issue.MessageText, + IssueCommentFormat.Markdown => !string.IsNullOrEmpty(issue.MessageMarkdown) ? issue.MessageMarkdown : issue.MessageText, + _ => throw new ArgumentOutOfRangeException(nameof(format)), + }; } /// @@ -97,10 +140,10 @@ public static string FileName(this IIssue issue) /// Returns a string with all patterns replaced by the values of . /// /// Pattern whose values should be replaced. - /// The following patterns are supported: + /// The following tokens are supported: /// /// - /// Pattern + /// Token /// Description /// /// @@ -112,6 +155,10 @@ public static string FileName(this IIssue issue) /// The value of . /// /// + /// {Identifier} + /// The value of . + /// + /// /// {Priority} /// The value of . /// @@ -148,6 +195,22 @@ public static string FileName(this IIssue issue) /// The value of . /// /// + /// {EndLine} + /// The value of . + /// + /// + /// {Column} + /// The value of . + /// + /// + /// {EndColumn} + /// The value of . + /// + /// + /// {FileLink} + /// The value of . + /// + /// /// {Rule} /// The value of . /// @@ -156,6 +219,10 @@ public static string FileName(this IIssue issue) /// The value of . /// /// + /// {Run} + /// The value of . + /// + /// /// {MessageText} /// The value of . /// @@ -182,6 +249,7 @@ public static string ReplaceIssuePattern(this string pattern, IIssue issue) pattern .Replace("{ProviderType}", issue.ProviderType) .Replace("{ProviderName}", issue.ProviderName) + .Replace("{Identifier}", issue.Identifier) .Replace("{Priority}", issue.Priority?.ToString()) .Replace("{PriorityName}", issue.PriorityName) .Replace("{ProjectPath}", issue.ProjectPath()) @@ -191,8 +259,13 @@ public static string ReplaceIssuePattern(this string pattern, IIssue issue) .Replace("{FileDirectory}", issue.FileDirectory()) .Replace("{FileName}", issue.FileName()) .Replace("{Line}", issue.Line?.ToString()) + .Replace("{EndLine}", issue.EndLine?.ToString()) + .Replace("{Column}", issue.Column?.ToString()) + .Replace("{EndColumn}", issue.EndColumn?.ToString()) + .Replace("{FileLink}", issue.FileLink?.ToString()) .Replace("{Rule}", issue.Rule) .Replace("{RuleUrl}", issue.RuleUrl?.ToString()) + .Replace("{Run}", issue.Run) .Replace("{MessageText}", issue.Message(IssueCommentFormat.PlainText)) .Replace("{MessageHtml}", issue.Message(IssueCommentFormat.Html)) .Replace("{MessageMarkdown}", issue.Message(IssueCommentFormat.Markdown)); diff --git a/src/Cake.Issues/IIssueProvider.cs b/src/Cake.Issues/IIssueProvider.cs index dd3231b46..63a6ccb62 100644 --- a/src/Cake.Issues/IIssueProvider.cs +++ b/src/Cake.Issues/IIssueProvider.cs @@ -5,7 +5,7 @@ /// /// Interface describing a provider for issues. /// - public interface IIssueProvider : IBaseIssueComponent + public interface IIssueProvider : IBaseIssueComponent { /// /// Gets the human friendly name of the issue provider. diff --git a/src/Cake.Issues/ILogFileFormat.cs b/src/Cake.Issues/ILogFileFormat.cs index 457bc8f12..663536495 100644 --- a/src/Cake.Issues/ILogFileFormat.cs +++ b/src/Cake.Issues/ILogFileFormat.cs @@ -20,7 +20,7 @@ public interface ILogFileFormat /// List of issues. IEnumerable ReadIssues( TIssueProvider issueProvider, - RepositorySettings repositorySettings, + IRepositorySettings repositorySettings, TSettings issueProviderSettings); } } diff --git a/src/Cake.Issues/IReadIssuesSettings.cs b/src/Cake.Issues/IReadIssuesSettings.cs new file mode 100644 index 000000000..8adb7ba94 --- /dev/null +++ b/src/Cake.Issues/IReadIssuesSettings.cs @@ -0,0 +1,18 @@ +namespace Cake.Issues +{ + /// + /// Interface for settings for reading issues. + /// + public interface IReadIssuesSettings : IRepositorySettings + { + /// + /// Gets or sets the name of the run. + /// + string Run { get; set; } + + /// + /// Gets or sets settings which can be used for resolving links to source files. + /// + FileLinkSettings FileLinkSettings { get; set; } + } +} diff --git a/src/Cake.Issues/IRepositorySettings.cs b/src/Cake.Issues/IRepositorySettings.cs new file mode 100644 index 000000000..0ce5a9e87 --- /dev/null +++ b/src/Cake.Issues/IRepositorySettings.cs @@ -0,0 +1,15 @@ +namespace Cake.Issues +{ + using Cake.Core.IO; + + /// + /// Interface for settings containing a path to a repository. + /// + public interface IRepositorySettings + { + /// + /// Gets the Root path of the repository. + /// + DirectoryPath RepositoryRoot { get; } + } +} diff --git a/src/Cake.Issues/Issue.cs b/src/Cake.Issues/Issue.cs index e699c512b..a7c7f958b 100644 --- a/src/Cake.Issues/Issue.cs +++ b/src/Cake.Issues/Issue.cs @@ -11,6 +11,8 @@ public class Issue : IIssue /// /// Initializes a new instance of the class. /// + /// The identifier of the issue. + /// The identifier needs to be identical across multiple runs of an issue provider for the same issue. /// The path to the project to which the file affected by the issue belongs. /// The path needs to be relative to the repository root. /// Can be null or if issue is not related to a project. @@ -21,6 +23,14 @@ public class Issue : IIssue /// null or if issue is not related to a change in a file. /// The line in the file where the issues has occurred. /// null if the issue affects the whole file or an asssembly. + /// The end of the line range in the file where the issues has occurred. + /// null if the issue affects the whole file, an asssembly or only a single line. + /// The column in the file where the issues has occurred. + /// null if the issue affects the whole file or an asssembly. + /// The end of the column range in the file where the issues has occurred. + /// null if the issue affects the whole file, an asssembly or only a single column. + /// Link to the position in the file where the issue ocurred. + /// null if no link is available. /// The message of the issue in plain text format. /// The message of the issue in Html format. /// The message of the issue in Markdown format. @@ -32,13 +42,19 @@ public class Issue : IIssue /// null or if issue has no specific rule ID. /// The URL containing information about the failing rule. /// null if no URL is available. + /// Gets the description of the run. /// The type of the issue provider. /// The human friendly name of the issue provider. public Issue( + string identifier, string projectFileRelativePath, string projectName, string affectedFileRelativePath, int? line, + int? endLine, + int? column, + int? endColumn, + Uri fileLink, string messageText, string messageHtml, string messageMarkdown, @@ -46,10 +62,15 @@ public Issue( string priorityName, string rule, Uri ruleUrl, + string run, string providerType, string providerName) { + identifier.NotNullOrWhiteSpace(nameof(identifier)); line?.NotNegativeOrZero(nameof(line)); + endLine?.NotNegativeOrZero(nameof(endLine)); + column?.NotNegativeOrZero(nameof(column)); + endColumn?.NotNegativeOrZero(nameof(endColumn)); messageText.NotNullOrWhiteSpace(nameof(messageText)); providerType.NotNullOrWhiteSpace(nameof(providerType)); providerName.NotNullOrWhiteSpace(nameof(providerName)); @@ -95,8 +116,38 @@ public Issue( throw new ArgumentOutOfRangeException(nameof(line), "Cannot specify a line while not specifying a file."); } + if (!line.HasValue && (column.HasValue || endColumn.HasValue)) + { + throw new ArgumentOutOfRangeException(nameof(column), $"Cannot specify a column while not specifying a line."); + } + + if (!line.HasValue && endLine.HasValue) + { + throw new ArgumentOutOfRangeException(nameof(endLine), $"Cannot specify the end of line range while not specifying start of line range."); + } + + if (line.HasValue && endLine.HasValue && line.Value > endLine.Value) + { + throw new ArgumentOutOfRangeException(nameof(endLine), $"Line range needs to end after start of range."); + } + + if (!column.HasValue && endColumn.HasValue) + { + throw new ArgumentOutOfRangeException(nameof(endColumn), $"Cannot specify the end of column range while not specifying start of column range."); + } + + if (column.HasValue && endColumn.HasValue && column.Value > endColumn.Value) + { + throw new ArgumentOutOfRangeException(nameof(endColumn), $"Column range needs to end after start of range."); + } + + this.Identifier = identifier; this.ProjectName = projectName; this.Line = line; + this.EndLine = endLine; + this.Column = column; + this.EndColumn = endColumn; + this.FileLink = fileLink; this.MessageText = messageText; this.MessageHtml = messageHtml; this.MessageMarkdown = messageMarkdown; @@ -104,10 +155,14 @@ public Issue( this.PriorityName = priorityName; this.Rule = rule; this.RuleUrl = ruleUrl; + this.Run = run; this.ProviderType = providerType; this.ProviderName = providerName; } + /// + public string Identifier { get; } + /// public FilePath ProjectFileRelativePath { get; } @@ -120,6 +175,18 @@ public Issue( /// public int? Line { get; } + /// + public int? EndLine { get; } + + /// + public int? Column { get; } + + /// + public int? EndColumn { get; } + + /// + public Uri FileLink { get; set; } + /// public string MessageText { get; } @@ -141,6 +208,9 @@ public Issue( /// public Uri RuleUrl { get; } + /// + public string Run { get; set; } + /// public string ProviderType { get; } diff --git a/src/Cake.Issues/IssueBuilder.cs b/src/Cake.Issues/IssueBuilder.cs index aee2041d6..5058deae7 100644 --- a/src/Cake.Issues/IssueBuilder.cs +++ b/src/Cake.Issues/IssueBuilder.cs @@ -7,6 +7,7 @@ /// public class IssueBuilder { + private readonly string identifier; private readonly string providerType; private readonly string providerName; private readonly string messageText; @@ -16,58 +17,79 @@ public class IssueBuilder private string projectName; private string filePath; private int? line; + private int? endLine; + private int? column; + private int? endColumn; + private Uri fileLink; private int? priority; private string priorityName; private string rule; private Uri ruleUrl; + private string run; /// /// Initializes a new instance of the class. /// + /// The identifier of the message. /// The message of the issue in plain text format. /// The type of the issue provider. /// The human friendly name of the issue provider. private IssueBuilder( + string identifier, string message, string providerType, string providerName) { +#pragma warning disable SA1123 // Do not place regions within elements + #region DupFinder Exclusion +#pragma warning restore SA1123 // Do not place regions within elements + + identifier.NotNullOrWhiteSpace(nameof(identifier)); message.NotNullOrWhiteSpace(nameof(message)); providerType.NotNullOrWhiteSpace(nameof(providerType)); providerName.NotNullOrWhiteSpace(nameof(providerName)); + #endregion + + this.identifier = identifier; this.messageText = message; this.providerType = providerType; this.providerName = providerName; } /// - /// Initiates the creation of a new . + /// Initiates the creation of a new with + /// as identifier. /// + /// Type of the issue provider which has the issue created. /// The message of the issue in plain text format. - /// The type of the issue provider. - /// The human friendly name of the issue provider. + /// Issue provider which has the issue created. /// Builder class for creating a new issue. - public static IssueBuilder NewIssue( + public static IssueBuilder NewIssue( string message, - string providerType, - string providerName) + T issueProvider) + where T : IIssueProvider { + if (issueProvider == null) + { + throw new ArgumentNullException(nameof(issueProvider)); + } + message.NotNullOrWhiteSpace(nameof(message)); - providerType.NotNullOrWhiteSpace(nameof(providerType)); - providerName.NotNullOrWhiteSpace(nameof(providerName)); - return new IssueBuilder(message, providerType, providerName); + return NewIssue(message, message, issueProvider); } /// /// Initiates the creation of a new . /// /// Type of the issue provider which has the issue created. + /// The identifier of the message. /// The message of the issue in plain text format. /// Issue provider which has the issue created. /// Builder class for creating a new issue. public static IssueBuilder NewIssue( + string identifier, string message, T issueProvider) where T : IIssueProvider @@ -79,7 +101,54 @@ public static IssueBuilder NewIssue( message.NotNullOrWhiteSpace(nameof(message)); - return new IssueBuilder(message, typeof(T).FullName, issueProvider.ProviderName); + return NewIssue(identifier, message, typeof(T).FullName, issueProvider.ProviderName); + } + + /// + /// Initiates the creation of a new with as identifier. + /// + /// The message of the issue in plain text format. + /// The type of the issue provider. + /// The human friendly name of the issue provider. + /// Builder class for creating a new issue. + public static IssueBuilder NewIssue( + string message, + string providerType, + string providerName) + { + message.NotNullOrWhiteSpace(nameof(message)); + providerType.NotNullOrWhiteSpace(nameof(providerType)); + providerName.NotNullOrWhiteSpace(nameof(providerName)); + + return NewIssue(message, message, providerType, providerName); + } + + /// + /// Initiates the creation of a new . + /// + /// The identifier of the message. + /// The message of the issue in plain text format. + /// The type of the issue provider. + /// The human friendly name of the issue provider. + /// Builder class for creating a new issue. + public static IssueBuilder NewIssue( + string identifier, + string message, + string providerType, + string providerName) + { +#pragma warning disable SA1123 // Do not place regions within elements + #region DupFinder Exclusion +#pragma warning restore SA1123 // Do not place regions within elements + + identifier.NotNullOrWhiteSpace(nameof(identifier)); + message.NotNullOrWhiteSpace(nameof(message)); + providerType.NotNullOrWhiteSpace(nameof(providerType)); + providerName.NotNullOrWhiteSpace(nameof(providerName)); + + #endregion + + return new IssueBuilder(identifier, message, providerType, providerName); } /// @@ -179,8 +248,73 @@ public IssueBuilder InFile(string filePath, int? line) { line?.NotNegativeOrZero(nameof(line)); + this.InFile(filePath, line, null); + + return this; + } + + /// + /// Sets the path to the file affected by the issue and the line and column in the file where the issues has occurred. + /// + /// The path to the file affacted by the issue. + /// The path needs to be relative to the repository root. + /// null or if issue is not related to a change in a file. + /// The line in the file where the issues has occurred. + /// null if the issue affects the whole file or an asssembly. + /// The column in the file where the issues has occurred. + /// null if the issue affects the whole file or an asssembly. + /// Issue Builder instance. + public IssueBuilder InFile(string filePath, int? line, int? column) + { + line?.NotNegativeOrZero(nameof(line)); + column?.NotNegativeOrZero(nameof(column)); + + this.InFile(filePath, line, null, column, null); + + return this; + } + + /// + /// Sets the path to the file affected by the issue and the line and column in the file where the issues has occurred. + /// + /// The path to the file affacted by the issue. + /// The path needs to be relative to the repository root. + /// null or if issue is not related to a change in a file. + /// The line in the file where the issues has occurred. + /// null if the issue affects the whole file or an asssembly. + /// The end of the line range in the file where the issues has occurred. + /// null if the issue affects the whole file, an asssembly or only a single line. + /// The column in the file where the issues has occurred. + /// null if the issue affects the whole file or an asssembly. + /// The end of the column range in the file where the issues has occurred. + /// null if the issue affects the whole file, an asssembly or only a single column. + /// Issue Builder instance. + public IssueBuilder InFile(string filePath, int? startLine, int? endLine, int? startColumn, int? endColumn) + { + startLine?.NotNegativeOrZero(nameof(startLine)); + endLine?.NotNegativeOrZero(nameof(endLine)); + startColumn?.NotNegativeOrZero(nameof(startColumn)); + endColumn?.NotNegativeOrZero(nameof(endColumn)); + this.filePath = filePath; - this.line = line; + this.line = startLine; + this.endLine = endLine; + this.column = startColumn; + this.endColumn = endColumn; + + return this; + } + + /// + /// Sets the the link to the position in the file where the issue ocurred. + /// + /// Link to the position in the file where the issue ocurred. + /// Issue Builder instance. + public IssueBuilder WithFileLink(Uri fileLink) + { + fileLink.NotNull(nameof(fileLink)); + + this.fileLink = fileLink; return this; } @@ -240,6 +374,20 @@ public IssueBuilder OfRule(string name, Uri uri) return this; } + /// + /// Sets the name of the run where the issue was reported. + /// + /// The name of the run where the issue was reported. + /// Issue Builder instance. + public IssueBuilder ForRun(string run) + { + run.NotNullOrWhiteSpace(nameof(run)); + + this.run = run; + + return this; + } + /// /// Creates a new . /// @@ -248,10 +396,15 @@ public IIssue Create() { return new Issue( + this.identifier, this.projectFileRelativePath, this.projectName, this.filePath, this.line, + this.endLine, + this.column, + this.endColumn, + this.fileLink, this.messageText, this.messageHtml, this.messageMarkdown, @@ -259,6 +412,7 @@ public IIssue Create() this.priorityName, this.rule, this.ruleUrl, + this.run, this.providerType, this.providerName); } diff --git a/src/Cake.Issues/IssuesAliasConstants.cs b/src/Cake.Issues/IssuesAliasConstants.cs index 4ecf0d797..032efca2d 100644 --- a/src/Cake.Issues/IssuesAliasConstants.cs +++ b/src/Cake.Issues/IssuesAliasConstants.cs @@ -25,6 +25,11 @@ public static class IssuesAliasConstants /// public const string SerializationCakeAliasCategory = "Issue Serialization"; + /// + /// Category to use for all Cake aliases providing functionality for linking to files. + /// + public const string FileLinkingCakeAliasCategory = "File Linking"; + /// /// Category to use for all Cake aliases creating issue providers. /// diff --git a/src/Cake.Issues/IssuesReader.cs b/src/Cake.Issues/IssuesReader.cs index 7751cbc34..d64e03af9 100644 --- a/src/Cake.Issues/IssuesReader.cs +++ b/src/Cake.Issues/IssuesReader.cs @@ -11,7 +11,7 @@ public class IssuesReader { private readonly ICakeLog log; private readonly List issueProviders = new List(); - private readonly RepositorySettings settings; + private readonly IReadIssuesSettings settings; /// /// Initializes a new instance of the class. @@ -22,7 +22,7 @@ public class IssuesReader public IssuesReader( ICakeLog log, IEnumerable issueProviders, - RepositorySettings settings) + IReadIssuesSettings settings) { log.NotNull(nameof(log)); settings.NotNull(nameof(settings)); @@ -59,6 +59,16 @@ public IEnumerable ReadIssues() currentIssues.Count, providerName); + currentIssues.ForEach(x => + { + x.Run = this.settings.Run; + + if (this.settings.FileLinkSettings != null) + { + x.FileLink = this.settings.FileLinkSettings.GetFileLink(x); + } + }); + issues.AddRange(currentIssues); } else diff --git a/src/Cake.Issues/ReadIssuesSettings.cs b/src/Cake.Issues/ReadIssuesSettings.cs index d3c52dcec..1a4c4e6b9 100644 --- a/src/Cake.Issues/ReadIssuesSettings.cs +++ b/src/Cake.Issues/ReadIssuesSettings.cs @@ -5,7 +5,7 @@ /// /// Settings for reading issues. /// - public class ReadIssuesSettings : RepositorySettings + public class ReadIssuesSettings : RepositorySettings, IReadIssuesSettings { /// /// Initializes a new instance of the class. @@ -15,5 +15,11 @@ public ReadIssuesSettings(DirectoryPath repositoryRoot) : base(repositoryRoot) { } + + /// + public string Run { get; set; } + + /// + public FileLinkSettings FileLinkSettings { get; set; } } } diff --git a/src/Cake.Issues/RepositorySettings.cs b/src/Cake.Issues/RepositorySettings.cs index 24f7e9bb2..e045200fb 100644 --- a/src/Cake.Issues/RepositorySettings.cs +++ b/src/Cake.Issues/RepositorySettings.cs @@ -5,7 +5,7 @@ /// /// Settings containing a path to a repository. /// - public class RepositorySettings + public class RepositorySettings : IRepositorySettings { /// /// Initializes a new instance of the class. @@ -18,9 +18,7 @@ public RepositorySettings(DirectoryPath repositoryRoot) this.RepositoryRoot = repositoryRoot; } - /// - /// Gets the Root path of the repository. - /// + /// public DirectoryPath RepositoryRoot { get; } } } diff --git a/src/Cake.Issues/Serialization/IssueDeserializationExtensions.cs b/src/Cake.Issues/Serialization/IssueDeserializationExtensions.cs index 1179b260c..ced6f66cd 100644 --- a/src/Cake.Issues/Serialization/IssueDeserializationExtensions.cs +++ b/src/Cake.Issues/Serialization/IssueDeserializationExtensions.cs @@ -123,13 +123,12 @@ private static Issue DeserializeJsonDataToIssue(JsonData data) if (data.ContainsKey("Version")) { var version = (int)data["Version"]; - switch (version) + return version switch { - case 2: - return JsonMapper.ToObject(data.ToJson()).ToIssue(); - default: - throw new Exception($"Not supported issue serialization format {version}"); - } + 2 => JsonMapper.ToObject(data.ToJson()).ToIssue(), + 3 => JsonMapper.ToObject(data.ToJson()).ToIssue(), + _ => throw new Exception($"Not supported issue serialization format {version}"), + }; } else { diff --git a/src/Cake.Issues/Serialization/IssueSerializationExtensions.cs b/src/Cake.Issues/Serialization/IssueSerializationExtensions.cs index b66a33a31..01901bdd0 100644 --- a/src/Cake.Issues/Serialization/IssueSerializationExtensions.cs +++ b/src/Cake.Issues/Serialization/IssueSerializationExtensions.cs @@ -70,20 +70,25 @@ public static void SerializeToJsonFile(this IEnumerable issues, FilePath } /// - /// Converts an to a . + /// Converts an to a . /// /// Issue which should be converted. /// Converted issue. - internal static SerializableIssueV2 ToSerializableIssue(this IIssue issue) + internal static SerializableIssueV3 ToSerializableIssue(this IIssue issue) { issue.NotNull(nameof(issue)); - return new SerializableIssueV2 + return new SerializableIssueV3 { + Identifier = issue.Identifier, ProjectFileRelativePath = issue.ProjectFileRelativePath?.FullPath, ProjectName = issue.ProjectName, AffectedFileRelativePath = issue.AffectedFileRelativePath?.FullPath, Line = issue.Line, + EndLine = issue.EndLine, + Column = issue.Column, + EndColumn = issue.EndColumn, + FileLink = issue.FileLink?.ToString(), MessageText = issue.MessageText, MessageMarkdown = issue.MessageMarkdown, MessageHtml = issue.MessageHtml, @@ -91,6 +96,7 @@ internal static SerializableIssueV2 ToSerializableIssue(this IIssue issue) PriorityName = issue.PriorityName, Rule = issue.Rule, RuleUrl = issue.RuleUrl?.ToString(), + Run = issue.Run, ProviderType = issue.ProviderType, ProviderName = issue.ProviderName, }; diff --git a/src/Cake.Issues/Serialization/SerializableIssueExtensions.cs b/src/Cake.Issues/Serialization/SerializableIssueExtensions.cs index 9b722e83c..24b479dd9 100644 --- a/src/Cake.Issues/Serialization/SerializableIssueExtensions.cs +++ b/src/Cake.Issues/Serialization/SerializableIssueExtensions.cs @@ -27,10 +27,15 @@ internal static Issue ToIssue(this SerializableIssue serializableIssue) } return new Issue( + serializableIssue.Message, serializableIssue.ProjectFileRelativePath, serializableIssue.ProjectName, serializableIssue.AffectedFileRelativePath, serializableIssue.Line, + null, + null, + null, + null, serializableIssue.Message, null, null, @@ -38,6 +43,7 @@ internal static Issue ToIssue(this SerializableIssue serializableIssue) serializableIssue.PriorityName, serializableIssue.Rule, ruleUrl, + null, serializableIssue.ProviderType, serializableIssue.ProviderName); diff --git a/src/Cake.Issues/Serialization/SerializableIssueV2Extensions.cs b/src/Cake.Issues/Serialization/SerializableIssueV2Extensions.cs index 4ab0491f8..3c661919b 100644 --- a/src/Cake.Issues/Serialization/SerializableIssueV2Extensions.cs +++ b/src/Cake.Issues/Serialization/SerializableIssueV2Extensions.cs @@ -27,10 +27,15 @@ internal static Issue ToIssue(this SerializableIssueV2 serializableIssue) } return new Issue( + serializableIssue.MessageText, serializableIssue.ProjectFileRelativePath, serializableIssue.ProjectName, serializableIssue.AffectedFileRelativePath, serializableIssue.Line, + null, + null, + null, + null, serializableIssue.MessageText, serializableIssue.MessageHtml, serializableIssue.MessageMarkdown, @@ -38,6 +43,7 @@ internal static Issue ToIssue(this SerializableIssueV2 serializableIssue) serializableIssue.PriorityName, serializableIssue.Rule, ruleUrl, + null, serializableIssue.ProviderType, serializableIssue.ProviderName); diff --git a/src/Cake.Issues/Serialization/SerializableIssueV3.cs b/src/Cake.Issues/Serialization/SerializableIssueV3.cs new file mode 100644 index 000000000..5c9ac648a --- /dev/null +++ b/src/Cake.Issues/Serialization/SerializableIssueV3.cs @@ -0,0 +1,99 @@ +namespace Cake.Issues.Serialization +{ + using System.Runtime.Serialization; + + /// + /// Class for serializing and deserializing an instance. + /// + [DataContract] + internal class SerializableIssueV3 + { + /// + /// Gets the version of the serialization format. + /// + [DataMember] + public int Version + { + get + { + return 3; + } + } + + /// + [DataMember] + public string Identifier { get; set; } + + /// + [DataMember] + public string ProjectFileRelativePath { get; set; } + + /// + [DataMember] + public string ProjectName { get; set; } + + /// + [DataMember] + public string AffectedFileRelativePath { get; set; } + + /// + [DataMember] + public int? Line { get; set; } + + /// + [DataMember] + public int? EndLine { get; set; } + + /// + [DataMember] + public int? Column { get; set; } + + /// + [DataMember] + public int? EndColumn { get; set; } + + /// + [DataMember] + public string FileLink { get; set; } + + /// + [DataMember] + public string MessageText { get; set; } + + /// + [DataMember] + public string MessageMarkdown { get; set; } + + /// + [DataMember] + public string MessageHtml { get; set; } + + /// + [DataMember] + public int? Priority { get; set; } + + /// + [DataMember] + public string PriorityName { get; set; } + + /// + [DataMember] + public string Rule { get; set; } + + /// + [DataMember] + public string RuleUrl { get; set; } + + /// + [DataMember] + public string ProviderType { get; set; } + + /// + [DataMember] + public string ProviderName { get; set; } + + /// + [DataMember] + public string Run { get; set; } + } +} diff --git a/src/Cake.Issues/Serialization/SerializableIssueV3Extensions.cs b/src/Cake.Issues/Serialization/SerializableIssueV3Extensions.cs new file mode 100644 index 000000000..e0ea7c239 --- /dev/null +++ b/src/Cake.Issues/Serialization/SerializableIssueV3Extensions.cs @@ -0,0 +1,59 @@ +namespace Cake.Issues.Serialization +{ + using System; + + /// + /// Extensions for . + /// + internal static class SerializableIssueV3Extensions + { + /// + /// Converts a to an . + /// + /// Issue which should be converted. + /// Converted issue. + internal static Issue ToIssue(this SerializableIssueV3 serializableIssue) + { +#pragma warning disable SA1123 // Do not place regions within elements + #region DupFinder Exclusion +#pragma warning restore SA1123 // Do not place regions within elements + + serializableIssue.NotNull(nameof(serializableIssue)); + + Uri ruleUrl = null; + if (!string.IsNullOrWhiteSpace(serializableIssue.RuleUrl)) + { + ruleUrl = new Uri(serializableIssue.RuleUrl); + } + + Uri fileLink = null; + if (!string.IsNullOrWhiteSpace(serializableIssue.FileLink)) + { + fileLink = new Uri(serializableIssue.FileLink); + } + + return new Issue( + serializableIssue.Identifier, + serializableIssue.ProjectFileRelativePath, + serializableIssue.ProjectName, + serializableIssue.AffectedFileRelativePath, + serializableIssue.Line, + serializableIssue.EndLine, + serializableIssue.Column, + serializableIssue.EndColumn, + fileLink, + serializableIssue.MessageText, + serializableIssue.MessageHtml, + serializableIssue.MessageMarkdown, + serializableIssue.Priority, + serializableIssue.PriorityName, + serializableIssue.Rule, + ruleUrl, + serializableIssue.Run, + serializableIssue.ProviderType, + serializableIssue.ProviderName); + + #endregion + } + } +} diff --git a/src/Cake.Issues/StringPathExtensions.cs b/src/Cake.Issues/StringPathExtensions.cs index fbb7990f0..07ea8ff20 100644 --- a/src/Cake.Issues/StringPathExtensions.cs +++ b/src/Cake.Issues/StringPathExtensions.cs @@ -103,7 +103,7 @@ public static string NormalizePath(this string path) /// String to which should be added. /// String which should be added to . /// with the minimal concatenation of . - internal static string WithEnding(this string value, string ending) + public static string WithEnding(this string value, string ending) { if (value == null) { diff --git a/src/Cake.Issues/UriExtensions.cs b/src/Cake.Issues/UriExtensions.cs new file mode 100644 index 000000000..2d6287cea --- /dev/null +++ b/src/Cake.Issues/UriExtensions.cs @@ -0,0 +1,36 @@ +namespace Cake.Issues +{ + using System; + using System.Globalization; + using System.Linq; + + /// + /// Extensions for the class. + /// + internal static class UriExtensions + { + /// + /// Appends paths to an URI. + /// + /// URI to which the paths should be appended. + /// Paths to append. + /// URI with appended paths. + public static Uri Append(this Uri uri, params string[] paths) + { + uri.NotNull(nameof(uri)); + + return + new Uri( + paths + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Aggregate( + uri.AbsoluteUri, + (current, path) => + string.Format( + CultureInfo.InvariantCulture, + "{0}/{1}", + current.TrimEnd('/'), + path.TrimStart('/')))); + } + } +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..a95ac958c --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,5 @@ + + + latest + + \ No newline at end of file