diff --git a/Octokit.Tests.Integration/Clients/SearchClientTests.cs b/Octokit.Tests.Integration/Clients/SearchClientTests.cs index 3292c39090..8ae982509e 100644 --- a/Octokit.Tests.Integration/Clients/SearchClientTests.cs +++ b/Octokit.Tests.Integration/Clients/SearchClientTests.cs @@ -15,7 +15,7 @@ public SearchClientTests() _gitHubClient = Helper.GetAuthenticatedClient(); } - [Fact] + [IntegrationTest] public async Task SearchForCSharpRepositories() { var request = new SearchRepositoriesRequest("csharp"); @@ -24,7 +24,7 @@ public async Task SearchForCSharpRepositories() Assert.NotEmpty(repos.Items); } - [Fact] + [IntegrationTest] public async Task SearchForGitHub() { var request = new SearchUsersRequest("github"); @@ -33,7 +33,7 @@ public async Task SearchForGitHub() Assert.NotEmpty(repos.Items); } - [Fact] + [IntegrationTest] public async Task SearchForFunctionInCode() { var request = new SearchCodeRequest("addClass", "jquery", "jquery"); @@ -43,7 +43,7 @@ public async Task SearchForFunctionInCode() Assert.NotEmpty(repos.Items); } - [Fact] + [IntegrationTest] public async Task SearchForFileNameInCode() { var request = new SearchCodeRequest("GitHub") @@ -57,7 +57,7 @@ public async Task SearchForFileNameInCode() Assert.NotEmpty(repos.Items); } - [Fact] + [IntegrationTest] public async Task SearchForWordInCode() { var request = new SearchIssuesRequest("windows"); @@ -74,7 +74,7 @@ public async Task SearchForWordInCode() Assert.NotEmpty(repos.Items); } - [Fact] + [IntegrationTest] public async Task SearchForOpenIssues() { var request = new SearchIssuesRequest("phone"); @@ -86,7 +86,7 @@ public async Task SearchForOpenIssues() Assert.NotEmpty(issues.Items); } - [Fact(Skip = "see https://github.com/octokit/octokit.net/issues/1082 for investigating this failing test")] + [IntegrationTest] public async Task SearchForAllIssues() { var request = new SearchIssuesRequest("phone"); @@ -97,8 +97,8 @@ public async Task SearchForAllIssues() Assert.NotEmpty(issues.Items); } - [Fact] - public async Task SearchForAllIssuesWithouTaskUsingTerm() + [IntegrationTest] + public async Task SearchForAllIssuesWithoutUsingTerm() { var request = new SearchIssuesRequest(); request.Repos.Add("caliburn-micro/caliburn.micro"); @@ -112,7 +112,7 @@ public async Task SearchForAllIssuesWithouTaskUsingTerm() Assert.NotEmpty(openedIssues); } - [Fact] + [IntegrationTest] public async Task SearchForAllIssuesUsingTerm() { var request = new SearchIssuesRequest("phone"); @@ -126,4 +126,392 @@ public async Task SearchForAllIssuesUsingTerm() Assert.NotEmpty(closedIssues); Assert.NotEmpty(openedIssues); } + + [IntegrationTest] + public async Task SearchForMergedPullRequests() + { + var allRequest = new SearchIssuesRequest(); + allRequest.Repos.Add("octokit", "octokit.net"); + allRequest.Type = IssueTypeQualifier.PullRequest; + + var mergedRequest = new SearchIssuesRequest(); + mergedRequest.Repos.Add("octokit", "octokit.net"); + mergedRequest.Is = new List { IssueIsQualifier.PullRequest, IssueIsQualifier.Merged }; + + var allPullRequests = await _gitHubClient.Search.SearchIssues(allRequest); + var mergedPullRequests = await _gitHubClient.Search.SearchIssues(mergedRequest); + + Assert.NotEmpty(allPullRequests.Items); + Assert.NotEmpty(mergedPullRequests.Items); + Assert.NotEqual(allPullRequests.TotalCount, mergedPullRequests.TotalCount); + } + + [IntegrationTest] + public async Task SearchForMissingMetadata() + { + var allRequest = new SearchIssuesRequest(); + allRequest.Repos.Add("octokit", "octokit.net"); + + var noAssigneeRequest = new SearchIssuesRequest(); + noAssigneeRequest.Repos.Add("octokit", "octokit.net"); + noAssigneeRequest.No = IssueNoMetadataQualifier.Assignee; + + var allIssues = await _gitHubClient.Search.SearchIssues(allRequest); + var noAssigneeIssues = await _gitHubClient.Search.SearchIssues(noAssigneeRequest); + + Assert.NotEmpty(allIssues.Items); + Assert.NotEmpty(noAssigneeIssues.Items); + Assert.NotEqual(allIssues.TotalCount, noAssigneeIssues.TotalCount); + } + + [IntegrationTest] + public async Task SearchForExcludedAuthor() + { + var author = "shiftkey"; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Author = author; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Author = author + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedAssignee() + { + var assignee = "shiftkey"; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Assignee = assignee; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Assignee = assignee + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedMentions() + { + var mentioned = "shiftkey"; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Mentions = mentioned; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Mentions = mentioned + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedCommenter() + { + var commenter = "shiftkey"; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Commenter = commenter; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Commenter = commenter + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedInvolves() + { + var involves = "shiftkey"; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Involves = involves; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Involves = involves + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedState() + { + var state = ItemState.Open; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.State = state; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + State = state + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedLabels() + { + var label1 = "up-for-grabs"; + var label2 = "feature"; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Labels = new[] { label1, label2 }; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Labels = new[] { label1, label2 } + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedLanguage() + { + var language = Language.CSharp; + + // Search for issues by include filter + var request = new SearchIssuesRequest("octokit"); + request.Language = language; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest("octokit"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Language = language + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedStatus() + { + var status = CommitState.Success; + + // Search for issues by include filter + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Status = status; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues by exclude filter + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Status = status + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedHead() + { + var branch = "search-issues"; + + // Search for issues by source branch + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Head = branch; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues excluding source branch + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Head = branch + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } + + [IntegrationTest] + public async Task SearchForExcludedBase() + { + var branch = "master"; + + // Search for issues by target branch + var request = new SearchIssuesRequest(); + request.Repos.Add("octokit", "octokit.net"); + request.Base = branch; + + var issues = await _gitHubClient.Search.SearchIssues(request); + + // Ensure we found issues + Assert.NotEmpty(issues.Items); + + // Search for issues excluding target branch + var excludeRequest = new SearchIssuesRequest(); + excludeRequest.Repos.Add("octokit", "octokit.net"); + excludeRequest.Exclusions = new SearchIssuesRequestExclusions + { + Base = branch + }; + + var otherIssues = await _gitHubClient.Search.SearchIssues(excludeRequest); + + // Ensure we found issues + Assert.NotEmpty(otherIssues.Items); + + // Ensure no items from the first search are in the results for the second + Assert.DoesNotContain(issues.Items, x1 => otherIssues.Items.Any(x2 => x2.Id == x1.Id)); + } } diff --git a/Octokit.Tests/Clients/SearchClientTests.cs b/Octokit.Tests/Clients/SearchClientTests.cs index ef3e7afa07..ce14ea076a 100644 --- a/Octokit.Tests/Clients/SearchClientTests.cs +++ b/Octokit.Tests/Clients/SearchClientTests.cs @@ -806,12 +806,12 @@ public void TestingTheTypeQualifier_Issue() } [Fact] - public void TestingTheTypeQualifier_PR() + public void TestingTheTypeQualifier_PullRequest() { var connection = Substitute.For(); var client = new SearchClient(connection); var request = new SearchIssuesRequest("something"); - request.Type = IssueTypeQualifier.PR; + request.Type = IssueTypeQualifier.PullRequest; client.SearchIssues(request); @@ -967,7 +967,7 @@ public void TestingTheLanguageQualifier() connection.Received().Get( Arg.Is(u => u.ToString() == "search/issues"), - Arg.Is>(d => d["q"] == "something+language:CSharp")); + Arg.Is>(d => d["q"] == "something+language:C#")); } [Fact] diff --git a/Octokit.Tests/Models/SearchIssuesRequestExclusionsTests.cs b/Octokit.Tests/Models/SearchIssuesRequestExclusionsTests.cs new file mode 100644 index 0000000000..0cace60eb1 --- /dev/null +++ b/Octokit.Tests/Models/SearchIssuesRequestExclusionsTests.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Octokit; +using Octokit.Tests.Helpers; +using Xunit; + +public class SearchIssuesRequestExclusionsTests +{ + public class TheExclusionsMergedQualifiersMethod + { + [Fact] + public void HandlesStringAttributesCorrectly() + { + var stringProperties = new Dictionary>() + { + { "author:", (x,value) => x.Author = value }, + { "assignee:", (x,value) => x.Assignee = value }, + { "mentions:", (x,value) => x.Mentions = value }, + { "commenter:", (x,value) => x.Commenter = value }, + { "involves:", (x,value) => x.Involves = value }, + { "head:", (x,value) => x.Head = value }, + { "base:", (x,value) => x.Base = value } + }; + + foreach (var property in stringProperties) + { + var request = new SearchIssuesRequestExclusions(); + + // Ensure the specified parameter does not exist when not set + Assert.False(request.MergedQualifiers().Any(x => x.Contains(property.Key))); + + // Set the parameter + property.Value(request, "blah"); + + // Ensure the specified parameter now exists + Assert.True(request.MergedQualifiers().Count(x => x.Contains(property.Key)) == 1); + } + } + + [Fact] + public void HandlesStateAttributeCorrectly() + { + var request = new SearchIssuesRequestExclusions(); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("-state:"))); + + request.State = ItemState.Closed; + Assert.True(request.MergedQualifiers().Contains("-state:closed")); + } + + [Fact] + public void HandlesExcludeLabelsAttributeCorrectly() + { + var request = new SearchIssuesRequestExclusions(); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("-label:"))); + + request.Labels = new[] { "label1", "label2" }; + + Assert.True(request.MergedQualifiers().Contains("-label:label1")); + Assert.True(request.MergedQualifiers().Contains("-label:label2")); + } + + [Fact] + public void HandlesLanguageAttributeCorrectly() + { + var request = new SearchIssuesRequestExclusions(); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("-language:"))); + + request.Language = Language.CSharp; + + Assert.True(request.MergedQualifiers().Contains("-language:C#")); + } + + [Fact] + public void HandlesStatusAttributeCorrectly() + { + var request = new SearchIssuesRequestExclusions(); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("-status:"))); + + request.Status = CommitState.Error; + + Assert.True(request.MergedQualifiers().Contains("-status:error")); + } + } +} \ No newline at end of file diff --git a/Octokit.Tests/Models/SearchIssuesRequestTests.cs b/Octokit.Tests/Models/SearchIssuesRequestTests.cs index 35bae08e13..a075237e44 100644 --- a/Octokit.Tests/Models/SearchIssuesRequestTests.cs +++ b/Octokit.Tests/Models/SearchIssuesRequestTests.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; using Octokit; using Octokit.Tests.Helpers; using Xunit; -internal class SearchIssuesRequestTests +public class SearchIssuesRequestTests { public class TheMergedQualifiersMethod { @@ -25,5 +27,156 @@ public void SortNotSpecifiedByDefault() Assert.True(string.IsNullOrWhiteSpace(request.Sort)); Assert.False(request.Parameters.ContainsKey("sort")); } + + [Fact] + public void HandlesStringAttributesCorrectly() + { + var stringProperties = new Dictionary> + { + { "author:", (x,value) => x.Author = value }, + { "assignee:", (x,value) => x.Assignee = value }, + { "mentions:", (x,value) => x.Mentions = value }, + { "commenter:", (x,value) => x.Commenter = value }, + { "involves:", (x,value) => x.Involves = value }, + { "team:", (x,value) => x.Team = value }, + { "head:", (x,value) => x.Head = value }, + { "base:", (x,value) => x.Base = value }, + { "user:", (x,value) => x.User = value } + }; + + foreach (var property in stringProperties) + { + var request = new SearchIssuesRequest("query"); + + // Ensure the specified parameter does not exist when not set + Assert.False(request.MergedQualifiers().Any(x => x.Contains(property.Key))); + + // Set the parameter + property.Value(request, "blah"); + + // Ensure the specified parameter now exists + Assert.True(request.MergedQualifiers().Count(x => x.Contains(property.Key)) == 1); + } + } + + [Fact] + public void HandlesDateRangeAttributesCorrectly() + { + var dateProperties = new Dictionary> + { + { "created:", (x,value) => x.Created = value }, + { "updated:", (x,value) => x.Updated = value }, + { "merged:", (x,value) => x.Merged = value }, + { "closed:", (x,value) => x.Closed = value } + }; + + foreach (var property in dateProperties) + { + var request = new SearchIssuesRequest("query"); + + // Ensure the specified parameter does not exist when not set + Assert.False(request.MergedQualifiers().Any(x => x.Contains(property.Key))); + + // Set the parameter + property.Value(request, DateRange.GreaterThan(DateTime.Today.AddDays(-7))); + + // Ensure the specified parameter now exists + Assert.True(request.MergedQualifiers().Count(x => x.Contains(property.Key)) == 1); + } + } + + [Fact] + public void HandlesInAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("in:"))); + + request.In = new List { IssueInQualifier.Body, IssueInQualifier.Comment }; + Assert.True(request.MergedQualifiers().Contains("in:body,comment")); + } + + [Fact] + public void HandlesStateAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("state:"))); + + request.State = ItemState.Closed; + Assert.True(request.MergedQualifiers().Contains("state:closed")); + } + + [Fact] + public void HandlesLabelsAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("label:"))); + + request.Labels = new[] { "label1", "label2" }; + Assert.True(request.MergedQualifiers().Contains("label:label1")); + Assert.True(request.MergedQualifiers().Contains("label:label2")); + } + + [Fact] + public void HandlesNoMetadataAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("no:"))); + + request.No = IssueNoMetadataQualifier.Milestone; + Assert.True(request.MergedQualifiers().Contains("no:milestone")); + } + + [Fact] + public void HandlesLanguageAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("language:"))); + + request.Language = Language.CSharp; + Assert.True(request.MergedQualifiers().Contains("language:C#")); + } + + [Fact] + public void HandlesIsAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("is:"))); + + request.Is = new List { IssueIsQualifier.Merged, IssueIsQualifier.PullRequest }; + Assert.True(request.MergedQualifiers().Contains("is:merged")); + Assert.True(request.MergedQualifiers().Contains("is:pr")); + } + + [Fact] + public void HandlesStatusAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("status:"))); + + request.Status = CommitState.Error; + Assert.True(request.MergedQualifiers().Contains("status:error")); + } + + [Fact] + public void HandlesCommentsAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("comments:"))); + + request.Comments = Range.GreaterThan(5); + Assert.True(request.MergedQualifiers().Contains("comments:>5")); + } + + [Fact] + public void HandlesRepoAttributeCorrectly() + { + var request = new SearchIssuesRequest("test"); + Assert.False(request.MergedQualifiers().Any(x => x.Contains("repo:"))); + + request.Repos.Add("myorg", "repo1"); + request.Repos.Add("myorg", "repo2"); + Assert.True(request.MergedQualifiers().Contains("repo:myorg/repo1")); + Assert.True(request.MergedQualifiers().Contains("repo:myorg/repo2")); + } } -} +} \ No newline at end of file diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 4d36e03aa4..1976074819 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -188,6 +188,7 @@ + diff --git a/Octokit/Models/Request/SearchIssuesRequest.cs b/Octokit/Models/Request/SearchIssuesRequest.cs index 1b9a630c63..a1392aedca 100644 --- a/Octokit/Models/Request/SearchIssuesRequest.cs +++ b/Octokit/Models/Request/SearchIssuesRequest.cs @@ -37,7 +37,7 @@ public SearchIssuesRequest(string term) : base(term) /// If not provided, results are sorted by best match. /// /// - /// http://developer.github.com/v3/search/#search-issues + /// https://help.github.com/articles/searching-issues/#sort-the-results /// public IssueSearchSort? SortField { get; set; } @@ -46,12 +46,21 @@ public override string Sort get { return SortField.ToParameter(); } } + /// + /// With this qualifier you can restrict the search to issues or pull request only. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-issues-or-pull-requests + /// + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + public IssueTypeQualifier? Type { get; set; } + /// /// Qualifies which fields are searched. With this qualifier you can restrict /// the search to just the title, body, comments, or any combination of these. /// /// - /// https://help.github.com/articles/searching-issues#search-in + /// https://help.github.com/articles/searching-issues/#scope-the-search-fields /// private IEnumerable _inQualifier; public IEnumerable In @@ -66,20 +75,11 @@ public IEnumerable In } } - /// - /// With this qualifier you can restrict the search to issues or pull request only. - /// - /// - /// https://help.github.com/articles/searching-issues#type - /// - [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] - public IssueTypeQualifier? Type { get; set; } - /// /// Finds issues created by a certain user. /// /// - /// https://help.github.com/articles/searching-issues#author + /// https://help.github.com/articles/searching-issues/#search-by-the-author-of-an-issue-or-pull-request /// public string Author { get; set; } @@ -87,7 +87,7 @@ public IEnumerable In /// Finds issues that are assigned to a certain user. /// /// - /// https://help.github.com/articles/searching-issues#assignee + /// https://help.github.com/articles/searching-issues/#search-by-the-assignee-of-an-issue-or-pull-request /// public string Assignee { get; set; } @@ -95,7 +95,7 @@ public IEnumerable In /// Finds issues that mention a certain user. /// /// - /// https://help.github.com/articles/searching-issues#mentions + /// https://help.github.com/articles/searching-issues/#search-by-a-mentioned-user-within-an-issue-or-pull-request /// public string Mentions { get; set; } @@ -103,7 +103,7 @@ public IEnumerable In /// Finds issues that a certain user commented on. /// /// - /// https://help.github.com/articles/searching-issues#commenter + /// https://help.github.com/articles/searching-issues/#search-by-a-commenter-within-an-issue-or-pull-request /// public string Commenter { get; set; } @@ -112,24 +112,32 @@ public IEnumerable In /// mention that user, or were commented on by that user. /// /// - /// https://help.github.com/articles/searching-issues#involves + /// https://help.github.com/articles/searching-issues/#search-by-a-user-thats-involved-within-an-issue-or-pull-request /// public string Involves { get; set; } + /// + /// Finds issues that @mention a team within the organization + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-a-team-thats-mentioned-within-an-issue-or-pull-request + /// + public string Team { get; set; } + /// /// Filter issues based on whether they’re open or closed. /// /// - /// https://help.github.com/articles/searching-issues#state + /// https://help.github.com/articles/searching-issues/#search-based-on-whether-an-issue-or-pull-request-is-open /// public ItemState? State { get; set; } private IEnumerable _labels; /// - /// Filters issues based on their labels. + /// Filters issues based on the labels assigned. /// /// - /// https://help.github.com/articles/searching-issues#labels + /// https://help.github.com/articles/searching-issues/#search-by-the-labels-on-an-issue /// public IEnumerable Labels { @@ -144,18 +152,45 @@ public IEnumerable Labels } /// - /// Searches for issues within repositories that match a certain language. + /// Searches for issues based on missing metadata. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-missing-metadata-on-an-issue-or-pull-request + /// + public IssueNoMetadataQualifier? No { get; set; } + + /// + /// Searches for issues in repositories that match a certain language. /// /// - /// https://help.github.com/articles/searching-issues#language + /// https://help.github.com/articles/searching-issues/#search-by-the-main-language-of-a-repository /// public Language? Language { get; set; } + private IEnumerable _is; + /// + /// Searches for issues using a more human syntax covering options like state, type, merged status, private/public repository + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-the-state-of-an-issue-or-pull-request + /// + public IEnumerable Is + { + get { return _is; } + set + { + if (value != null && value.Any()) + { + _is = value.Distinct().ToList(); + } + } + } + /// /// Filters issues based on times of creation. /// /// - /// https://help.github.com/articles/searching-issues#created-and-last-updated + /// https://help.github.com/articles/searching-issues/#search-based-on-when-an-issue-or-pull-request-was-created-or-last-updated /// public DateRange Created { get; set; } @@ -163,17 +198,50 @@ public IEnumerable Labels /// Filters issues based on times when they were last updated. /// /// - /// https://help.github.com/articles/searching-issues#created-and-last-updated + /// https://help.github.com/articles/searching-issues/#search-based-on-when-an-issue-or-pull-request-was-created-or-last-updated /// public DateRange Updated { get; set; } /// - /// Filters issues based on times when they were last merged + /// Filters pull requests based on times when they were last merged. /// /// /// https://help.github.com/articles/searching-issues/#search-based-on-when-a-pull-request-was-merged /// public DateRange Merged { get; set; } + + /// + /// Filters pull requests based on the status of the commits. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-commit-status + /// + public CommitState? Status { get; set; } + + /// + /// Filters pull requests based on the branch they came from. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-branch-names + /// + public string Head { get; set; } + + /// + /// Filters pull requests based on the branch they are merging into. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-branch-names + /// + public string Base { get; set; } + + /// + /// Filters issues based on times when they were last closed. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-when-an-issue-or-pull-request-was-closed + /// + public DateRange Closed { get; set; } + /// /// Filters issues based on the quantity of comments. /// @@ -183,33 +251,35 @@ public IEnumerable Labels public Range Comments { get; set; } /// - /// Limits searches to a specific user. + /// Limits searches to repositories owned by a certain user or organization. /// /// - /// https://help.github.com/articles/searching-issues#users-organizations-and-repositories + /// https://help.github.com/articles/searching-issues/#search-within-a-users-or-organizations-repositories /// public string User { get; set; } [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public RepositoryCollection Repos { get; set; } + public SearchIssuesRequestExclusions Exclusions { get; set; } + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public override IReadOnlyList MergedQualifiers() { var parameters = new List(); - if (In != null) - { - parameters.Add(string.Format(CultureInfo.InvariantCulture, "in:{0}", - string.Join(",", In.Select(i => i.ToParameter())))); - } - if (Type != null) { parameters.Add(string.Format(CultureInfo.InvariantCulture, "type:{0}", Type.ToParameter())); } + if (In != null) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "in:{0}", + string.Join(",", In.Select(i => i.ToParameter())))); + } + if (Author.IsNotBlank()) { parameters.Add(string.Format(CultureInfo.InvariantCulture, "author:{0}", Author)); @@ -235,6 +305,11 @@ public override IReadOnlyList MergedQualifiers() parameters.Add(string.Format(CultureInfo.InvariantCulture, "involves:{0}", Involves)); } + if (Team.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "team:{0}", Team)); + } + if (State.HasValue) { parameters.Add(string.Format(CultureInfo.InvariantCulture, "state:{0}", State.Value.ToParameter())); @@ -245,9 +320,19 @@ public override IReadOnlyList MergedQualifiers() parameters.AddRange(Labels.Select(label => string.Format(CultureInfo.InvariantCulture, "label:{0}", label))); } + if (No.HasValue) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "no:{0}", No.Value.ToParameter())); + } + if (Language != null) { - parameters.Add(string.Format(CultureInfo.InvariantCulture, "language:{0}", Language)); + parameters.Add(string.Format(CultureInfo.InvariantCulture, "language:{0}", Language.ToParameter())); + } + + if (Is != null) + { + parameters.AddRange(Is.Select(x => string.Format(CultureInfo.InvariantCulture, "is:{0}", x.ToParameter()))); } if (Created != null) @@ -259,10 +344,32 @@ public override IReadOnlyList MergedQualifiers() { parameters.Add(string.Format(CultureInfo.InvariantCulture, "updated:{0}", Updated)); } + if (Merged != null) { parameters.Add(string.Format(CultureInfo.InvariantCulture, "merged:{0}", Merged)); } + + if (Status.HasValue) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "status:{0}", Status.Value.ToParameter())); + } + + if (Head.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "head:{0}", Head)); + } + + if (Base.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "base:{0}", Base)); + } + + if (Closed != null) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "closed:{0}", Closed)); + } + if (Comments != null) { parameters.Add(string.Format(CultureInfo.InvariantCulture, "comments:{0}", Comments)); @@ -281,8 +388,13 @@ public override IReadOnlyList MergedQualifiers() throw new RepositoryFormatException(invalidFormatRepos); } - parameters.Add( - string.Join("+", Repos.Select(x => "repo:" + x))); + parameters.AddRange(Repos.Select(x => string.Format(CultureInfo.InvariantCulture, "repo:{0}", x))); + } + + // Add any exclusion parameters + if (Exclusions != null) + { + parameters.AddRange(Exclusions.MergedQualifiers()); } return new ReadOnlyCollection(parameters); @@ -292,7 +404,7 @@ internal string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, "Term: {0}", Term); + return string.Format(CultureInfo.InvariantCulture, "Search: {0} {1}", Term, string.Join(" ", MergedQualifiers())); } } } @@ -321,6 +433,17 @@ public enum IssueSearchSort Merged } + public enum IssueTypeQualifier + { + [Obsolete("Use IssueTypeQualifier.PullRequest instead")] + [Parameter(Value = "pr")] + PR, + [Parameter(Value = "pr")] + PullRequest, + [Parameter(Value = "issue")] + Issue + } + public enum IssueInQualifier { [Parameter(Value = "title")] @@ -331,12 +454,34 @@ public enum IssueInQualifier Comment } - public enum IssueTypeQualifier + public enum IssueIsQualifier { + [Parameter(Value = "open")] + Open, + [Parameter(Value = "closed")] + Closed, + [Parameter(Value = "merged")] + Merged, + [Parameter(Value = "unmerged")] + Unmerged, [Parameter(Value = "pr")] - PR, + PullRequest, [Parameter(Value = "issue")] - Issue + Issue, + [Parameter(Value = "private")] + Private, + [Parameter(Value = "public")] + Public + } + + public enum IssueNoMetadataQualifier + { + [Parameter(Value = "label")] + Label, + [Parameter(Value = "milestone")] + Milestone, + [Parameter(Value = "assignee")] + Assignee } [DebuggerDisplay("{DebuggerDisplay,nq}")] diff --git a/Octokit/Models/Request/SearchIssuesRequestExclusions.cs b/Octokit/Models/Request/SearchIssuesRequestExclusions.cs new file mode 100644 index 0000000000..9b0ecc6bd6 --- /dev/null +++ b/Octokit/Models/Request/SearchIssuesRequestExclusions.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Searching Issues + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class SearchIssuesRequestExclusions + { + /// + /// Exclusions for Issue Search + /// + public SearchIssuesRequestExclusions() + { + } + + /// + /// Excludes issues created by a certain user. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-the-author-of-an-issue-or-pull-request + /// + public string Author { get; set; } + + /// + /// Excludes issues that are assigned to a certain user. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-the-assignee-of-an-issue-or-pull-request + /// + public string Assignee { get; set; } + + /// + /// Excludes issues that mention a certain user. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-a-mentioned-user-within-an-issue-or-pull-request + /// + public string Mentions { get; set; } + + /// + /// Excludes issues that a certain user commented on. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-a-commenter-within-an-issue-or-pull-request + /// + public string Commenter { get; set; } + + /// + /// Excludes issues that were either created by a certain user, assigned to that user, + /// mention that user, or were commented on by that user. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-a-user-thats-involved-within-an-issue-or-pull-request + /// + public string Involves { get; set; } + + /// + /// Excludes issues based on open/closed state. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-whether-an-issue-or-pull-request-is-open + /// + public ItemState? State { get; set; } + + private IEnumerable _labels; + /// + /// Excludes issues based on the labels assigned. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-the-labels-on-an-issue + /// + public IEnumerable Labels + { + get { return _labels; } + set + { + if (value != null && value.Any()) + { + _labels = value.Distinct().ToList(); + } + } + } + + /// + /// Excludes issues in repositories that match a certain language. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-by-the-main-language-of-a-repository + /// + public Language? Language { get; set; } + + /// + /// Excludes pull requests based on the status of the commits. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-commit-status + /// + public CommitState? Status { get; set; } + + /// + /// Excludes pull requests based on the branch they came from. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-branch-names + /// + public string Head { get; set; } + + /// + /// Excludes pull requests based on the branch they are merging into. + /// + /// + /// https://help.github.com/articles/searching-issues/#search-based-on-branch-names + /// + public string Base { get; set; } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public IReadOnlyList MergedQualifiers() + { + var parameters = new List(); + + if (Author.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-author:{0}", Author)); + } + + if (Assignee.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-assignee:{0}", Assignee)); + } + + if (Mentions.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-mentions:{0}", Mentions)); + } + + if (Commenter.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-commenter:{0}", Commenter)); + } + + if (Involves.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-involves:{0}", Involves)); + } + + if (State.HasValue) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-state:{0}", State.Value.ToParameter())); + } + + if (Labels != null) + { + parameters.AddRange(Labels.Select(label => string.Format(CultureInfo.InvariantCulture, "-label:{0}", label))); + } + + if (Language != null) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-language:{0}", Language.ToParameter())); + } + + if (Status.HasValue) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-status:{0}", Status.Value.ToParameter())); + } + + if (Head.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-head:{0}", Head)); + } + + if (Base.IsNotBlank()) + { + parameters.Add(string.Format(CultureInfo.InvariantCulture, "-base:{0}", Base)); + } + + return new ReadOnlyCollection(parameters); + } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Exclusions: {0}", string.Join(" ", MergedQualifiers())); + } + } + } +} diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 4b28ca0679..044a32bf8c 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -459,6 +459,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index 4396b3c899..49bcf33899 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -468,6 +468,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 10562bbe35..b3ee893adf 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -464,6 +464,7 @@ + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 17259fe6bb..ce5c1dbaab 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -456,6 +456,7 @@ + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 73e365166b..77215bf7fa 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -463,6 +463,7 @@ + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index f4944f25b4..44e007ab42 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -129,6 +129,7 @@ +