diff --git a/Octokit.Reactive/Clients/IObservablePullRequestReviewCommentsClient.cs b/Octokit.Reactive/Clients/IObservablePullRequestReviewCommentsClient.cs new file mode 100644 index 00000000..89526242 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservablePullRequestReviewCommentsClient.cs @@ -0,0 +1,90 @@ +using System; +using System.Reactive; + +namespace Octokit.Reactive +{ + public interface IObservablePullRequestReviewCommentsClient + { + /// + /// Gets review comments for a specified pull request. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The list of s for the specified pull request + IObservable GetForPullRequest(string owner, string name, int number); + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The list of s for the specified repository + IObservable GetForRepository(string owner, string name); + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The sorting parameters + /// The list of s for the specified repository + IObservable GetForRepository(string owner, string name, PullRequestReviewCommentRequest request); + + /// + /// Gets a single pull request review comment by number. + /// + /// http://developer.github.com/v3/pulls/comments/#get-a-single-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The + IObservable GetComment(string owner, string name, int number); + + /// + /// Creates a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The Pull Request number + /// The comment + /// The created + IObservable Create(string owner, string name, int number, PullRequestReviewCommentCreate comment); + + /// + /// Creates a comment on a pull request review as a reply to another comment. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The comment + /// The created + IObservable CreateReply(string owner, string name, int number, PullRequestReviewCommentReplyCreate comment); + + /// + /// Edits a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#edit-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The edited comment + /// The edited + IObservable Edit(string owner, string name, int number, PullRequestReviewCommentEdit comment); + + /// + /// Deletes a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#delete-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// + IObservable Delete(string owner, string name, int number); + } +} diff --git a/Octokit.Reactive/Clients/IObservablePullRequestsClient.cs b/Octokit.Reactive/Clients/IObservablePullRequestsClient.cs index 739793c8..7fd79c95 100644 --- a/Octokit.Reactive/Clients/IObservablePullRequestsClient.cs +++ b/Octokit.Reactive/Clients/IObservablePullRequestsClient.cs @@ -5,6 +5,11 @@ namespace Octokit.Reactive { public interface IObservablePullRequestsClient { + /// + /// Client for managing comments. + /// + IObservablePullRequestReviewCommentsClient Comment { get; } + /// /// Gets a single Pull Request by number. /// diff --git a/Octokit.Reactive/Clients/ObservablePullRequestReviewCommentsClient.cs b/Octokit.Reactive/Clients/ObservablePullRequestReviewCommentsClient.cs new file mode 100644 index 00000000..6c33ae3d --- /dev/null +++ b/Octokit.Reactive/Clients/ObservablePullRequestReviewCommentsClient.cs @@ -0,0 +1,152 @@ +using System; +using System.Reactive; +using System.Reactive.Threading.Tasks; +using Octokit.Reactive.Internal; + +namespace Octokit.Reactive +{ + public class ObservablePullRequestReviewCommentsClient : IObservablePullRequestReviewCommentsClient + { + readonly IPullRequestReviewCommentsClient _client; + readonly IConnection _connection; + + public ObservablePullRequestReviewCommentsClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, "client"); + + _client = client.PullRequest.Comment; + _connection = client.Connection; + } + + /// + /// Gets review comments for a specified pull request. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The list of s for the specified pull request + public IObservable GetForPullRequest(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _connection.GetAndFlattenAllPages(ApiUrls.PullRequestReviewComments(owner, name, number)); + } + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The list of s for the specified repository + public IObservable GetForRepository(string owner, string name) + { + return GetForRepository(owner, name, new PullRequestReviewCommentRequest()); + } + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The sorting parameters + /// The list of s for the specified repository + public IObservable GetForRepository(string owner, string name, PullRequestReviewCommentRequest request) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(request, "request"); + + return _connection.GetAndFlattenAllPages(ApiUrls.PullRequestReviewCommentsRepository(owner, name), request.ToParametersDictionary()); + } + + /// + /// Gets a single pull request review comment by number. + /// + /// http://developer.github.com/v3/pulls/comments/#get-a-single-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The + public IObservable GetComment(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _client.GetComment(owner, name, number).ToObservable(); + } + + /// + /// Creates a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The Pull Request number + /// The comment + /// The created + public IObservable Create(string owner, string name, int number, PullRequestReviewCommentCreate comment) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(comment, "comment"); + + return _client.Create(owner, name, number, comment).ToObservable(); + } + + /// + /// Creates a comment on a pull request review as a reply to another comment. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The comment + /// The created + public IObservable CreateReply(string owner, string name, int number, PullRequestReviewCommentReplyCreate comment) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(comment, "comment"); + + return _client.CreateReply(owner, name, number, comment).ToObservable(); + } + + /// + /// Edits a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#edit-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The edited comment + /// The edited + public IObservable Edit(string owner, string name, int number, PullRequestReviewCommentEdit comment) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(comment, "comment"); + + return _client.Edit(owner, name, number, comment).ToObservable(); + } + + /// + /// Deletes a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#delete-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// + public IObservable Delete(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _client.Delete(owner, name, number).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/Clients/ObservablePullRequestsClient.cs b/Octokit.Reactive/Clients/ObservablePullRequestsClient.cs index 33f749b2..ca8cd05d 100644 --- a/Octokit.Reactive/Clients/ObservablePullRequestsClient.cs +++ b/Octokit.Reactive/Clients/ObservablePullRequestsClient.cs @@ -9,12 +9,18 @@ namespace Octokit.Reactive readonly IPullRequestsClient _client; readonly IConnection _connection; + /// + /// Client for managing comments. + /// + public IObservablePullRequestReviewCommentsClient Comment { get; private set; } + public ObservablePullRequestsClient(IGitHubClient client) { Ensure.ArgumentNotNull(client, "client"); _client = client.Repository.PullRequest; _connection = client.Connection; + Comment = new ObservablePullRequestReviewCommentsClient(client); } /// diff --git a/Octokit.Reactive/IObservableGitHubClient.cs b/Octokit.Reactive/IObservableGitHubClient.cs index c8983ff8..e6374a0c 100644 --- a/Octokit.Reactive/IObservableGitHubClient.cs +++ b/Octokit.Reactive/IObservableGitHubClient.cs @@ -10,6 +10,7 @@ IObservableMiscellaneousClient Miscellaneous { get; } IObservableOauthClient Oauth { get; } IObservableOrganizationsClient Organization { get; } + IObservablePullRequestsClient PullRequest { get; } IObservableRepositoriesClient Repository { get; } IObservableGistsClient Gist { get; } IObservableReleasesClient Release { get; } diff --git a/Octokit.Reactive/ObservableGitHubClient.cs b/Octokit.Reactive/ObservableGitHubClient.cs index cba117f2..dc5c3d03 100644 --- a/Octokit.Reactive/ObservableGitHubClient.cs +++ b/Octokit.Reactive/ObservableGitHubClient.cs @@ -38,6 +38,7 @@ namespace Octokit.Reactive Notification = new ObservableNotificationsClient(gitHubClient); Oauth = new ObservableOauthClient(gitHubClient); Organization = new ObservableOrganizationsClient(gitHubClient); + PullRequest = new ObservablePullRequestsClient(gitHubClient); Repository = new ObservableRepositoriesClient(gitHubClient); SshKey = new ObservableSshKeysClient(gitHubClient); User = new ObservableUsersClient(gitHubClient); @@ -58,6 +59,7 @@ namespace Octokit.Reactive public IObservableMiscellaneousClient Miscellaneous { get; private set; } public IObservableOauthClient Oauth { get; private set; } public IObservableOrganizationsClient Organization { get; private set; } + public IObservablePullRequestsClient PullRequest { get; private set; } public IObservableRepositoriesClient Repository { get; private set; } public IObservableGistsClient Gist { get; private set; } public IObservableReleasesClient Release { get; private set; } diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj index e952a858..ee8075f0 100644 --- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj @@ -107,6 +107,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj index 33dfb655..dcb18a1b 100644 --- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj +++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj @@ -116,6 +116,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj index 03e99810..fccfe7e1 100644 --- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj @@ -111,6 +111,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index 18f89bee..43b5cbd0 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -108,6 +108,7 @@ + @@ -136,6 +137,7 @@ + diff --git a/Octokit.Tests.Integration/Clients/PullRequestReviewCommentsClientTests.cs b/Octokit.Tests.Integration/Clients/PullRequestReviewCommentsClientTests.cs new file mode 100644 index 00000000..b3da09e9 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/PullRequestReviewCommentsClientTests.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octokit; +using Octokit.Tests.Integration; +using Xunit; + +public class PullRequestReviewCommentsClientTests : IDisposable +{ + readonly IGitHubClient _gitHubClient; + readonly IPullRequestReviewCommentsClient _client; + readonly Repository _repository; + + const string branchName = "heads/new-branch"; + const string path = "CONTRIBUTING.md"; + + public PullRequestReviewCommentsClientTests() + { + _gitHubClient = new GitHubClient(new ProductHeaderValue("OctokitTests")) + { + Credentials = Helper.Credentials + }; + + _client = _gitHubClient.PullRequest.Comment; + + // We'll create a pull request that can be used by most tests + var repoName = Helper.MakeNameWithTimestamp("test-repo"); + _repository = CreateRepository(repoName).Result; + } + + [IntegrationTest] + public async Task CanCreateAndRetrieveComment() + { + var pullRequest = await CreatePullRequest(_repository); + + const string body = "A review comment message"; + const int position = 1; + + var createdComment = await CreateComment(body, position, pullRequest.Sha, pullRequest.Number); + + var commentFromGitHub = await _client.GetComment(Helper.UserName, _repository.Name, createdComment.Id); + + AssertComment(commentFromGitHub, body, position); + } + + [IntegrationTest] + public async Task CanEditComment() + { + var pullRequest = await CreatePullRequest(_repository); + + const string body = "A new review comment message"; + const int position = 1; + + var createdComment = await CreateComment(body, position, pullRequest.Sha, pullRequest.Number); + + var edit = new PullRequestReviewCommentEdit("Edited Comment"); + + var editedComment = await _client.Edit(Helper.UserName, _repository.Name, createdComment.Id, edit); + + var commentFromGitHub = await _client.GetComment(Helper.UserName, _repository.Name, editedComment.Id); + + AssertComment(commentFromGitHub, edit.Body, position); + } + + [IntegrationTest] + public async Task TimestampsAreUpdated() + { + var pullRequest = await CreatePullRequest(_repository); + + const string body = "A new review comment message"; + const int position = 1; + + var createdComment = await CreateComment(body, position, pullRequest.Sha, pullRequest.Number); + + Assert.Equal(createdComment.UpdatedAt, createdComment.CreatedAt); + + await Task.Delay(TimeSpan.FromSeconds(2)); + + var edit = new PullRequestReviewCommentEdit("Edited Comment"); + + var editedComment = await _client.Edit(Helper.UserName, _repository.Name, createdComment.Id, edit); + + Assert.NotEqual(editedComment.UpdatedAt, editedComment.CreatedAt); + } + + [IntegrationTest] + public async Task CanDeleteComment() + { + var pullRequest = await CreatePullRequest(_repository); + + const string body = "A new review comment message"; + const int position = 1; + + var createdComment = await CreateComment(body, position, pullRequest.Sha, pullRequest.Number); + + Assert.DoesNotThrow(async () => { await _client.Delete(Helper.UserName, _repository.Name, createdComment.Id); }); + } + + [IntegrationTest] + public async Task CanCreateReply() + { + var pullRequest = await CreatePullRequest(_repository); + + const string body = "Reply me!"; + const int position = 1; + + var createdComment = await CreateComment(body, position, pullRequest.Sha, pullRequest.Number); + + var reply = new PullRequestReviewCommentReplyCreate("Replied", createdComment.Id); + var createdReply = await _client.CreateReply(Helper.UserName, _repository.Name, pullRequest.Number, reply); + var createdReplyFromGitHub = await _client.GetComment(Helper.UserName, _repository.Name, createdReply.Id); + + AssertComment(createdReplyFromGitHub, reply.Body, position); + } + + [IntegrationTest] + public async Task CanGetForPullRequest() + { + var pullRequest = await CreatePullRequest(_repository); + + const int position = 1; + var commentsToCreate = new List { "Comment 1", "Comment 2", "Comment 3" }; + + await CreateComments(commentsToCreate, position, _repository.Name, pullRequest.Sha, pullRequest.Number); + + var pullRequestComments = await _client.GetAll(Helper.UserName, _repository.Name, pullRequest.Number); + + AssertComments(pullRequestComments, commentsToCreate, position); + } + + [IntegrationTest] + public async Task CanGetForRepository() + { + var pullRequest = await CreatePullRequest(_repository); + + const int position = 1; + var commentsToCreate = new List { "Comment One", "Comment Two" }; + + await CreateComments(commentsToCreate, position, _repository.Name, pullRequest.Sha, pullRequest.Number); + + var pullRequestComments = await _client.GetForRepository(Helper.UserName, _repository.Name); + + AssertComments(pullRequestComments, commentsToCreate, position); + } + + [IntegrationTest] + public async Task CanGetForRepositoryAscendingSort() + { + var pullRequest = await CreatePullRequest(_repository); + + const int position = 1; + var commentsToCreate = new [] { "Comment One", "Comment Two", "Comment Three" }; + + await CreateComments(commentsToCreate, position, _repository.Name, pullRequest.Sha, pullRequest.Number); + + var pullRequestComments = await _client.GetForRepository(Helper.UserName, _repository.Name, new PullRequestReviewCommentRequest { Direction = SortDirection.Ascending }); + + Assert.Equal(pullRequestComments.Select(x => x.Body), commentsToCreate); + } + + [IntegrationTest] + public async Task CanGetForRepositoryDescendingSort() + { + var pullRequest = await CreatePullRequest(_repository); + + const int position = 1; + var commentsToCreate = new [] { "Comment One", "Comment Two", "Comment Three", "Comment Four" }; + + await CreateComments(commentsToCreate, position, _repository.Name, pullRequest.Sha, pullRequest.Number); + + var pullRequestComments = await _client.GetForRepository(Helper.UserName, _repository.Name, new PullRequestReviewCommentRequest { Direction = SortDirection.Descending }); + + Assert.Equal(pullRequestComments.Select(x => x.Body), commentsToCreate.Reverse()); + } + + public void Dispose() + { + Helper.DeleteRepo(_repository); + } + + async Task CreateComment(string body, int position, string commitId, int number) + { + return await CreateComment(body, position, _repository.Name, commitId, number); + } + + async Task CreateComment(string body, int position, string repoName, string pullRequestCommitId, int pullRequestNumber) + { + var comment = new PullRequestReviewCommentCreate(body, pullRequestCommitId, path, position); + + var createdComment = await _client.Create(Helper.UserName, repoName, pullRequestNumber, comment); + + AssertComment(createdComment, body, position); + + return createdComment; + } + + async Task CreateComments(IEnumerable comments, int position, string repoName, string pullRequestCommitId, int pullRequestNumber) + { + foreach (var comment in comments) + { + await CreateComment(comment, position, repoName, pullRequestCommitId, pullRequestNumber); + await Task.Delay(TimeSpan.FromSeconds(2)); + } + } + + static void AssertComment(PullRequestReviewComment comment, string body, int position) + { + Assert.NotNull(comment); + Assert.Equal(body, comment.Body); + Assert.Equal(position, comment.Position); + } + + static void AssertComments(IReadOnlyList comments, List bodies, int position) + { + Assert.Equal(bodies.Count, comments.Count); + + for (var i = 0; i < bodies.Count; i = i + 1) + { + AssertComment(comments[i], bodies[i], position); + } + } + + async Task CreateRepository(string repoName) + { + return await _gitHubClient.Repository.Create(new NewRepository { Name = repoName, AutoInit = true }); + } + + /// + /// Creates the base state for testing (creates a repo, a commit in master, a branch, a commit in the branch and a pull request) + /// + /// + async Task CreatePullRequest(Repository repository) + { + var repoName = repository.Name; + + // Creating a commit in master + + var createdCommitInMaster = await CreateCommit(repoName, "Hello World!", "README.md", "heads/master", "A master commit message"); + + // Creating a branch + + var newBranch = new NewReference("refs/" + branchName, createdCommitInMaster.Sha); + await _gitHubClient.GitDatabase.Reference.Create(Helper.UserName, repoName, newBranch); + + // Creating a commit in the branch + + var createdCommitInBranch = await CreateCommit(repoName, "Hello from the fork!", path, branchName, "A branch commit message"); + + // Creating a pull request + + var pullRequest = new NewPullRequest("Nice title for the pull request", branchName, "master"); + var createdPullRequest = await _gitHubClient.PullRequest.Create(Helper.UserName, repoName, pullRequest); + + var data = new PullRequestData + { + Sha = createdCommitInBranch.Sha, + Number = createdPullRequest.Number, + }; + + return data; + } + + async Task CreateCommit(string repoName, string blobContent, string treePath, string reference, string commitMessage) + { + // Creating a blob + var blob = new NewBlob + { + Content = blobContent, + Encoding = EncodingType.Utf8 + }; + + var createdBlob = await _gitHubClient.GitDatabase.Blob.Create(Helper.UserName, repoName, blob); + + // Creating a tree + var newTree = new NewTree(); + newTree.Tree.Add(new NewTreeItem + { + Type = TreeType.Blob, + Mode = FileMode.File, + Path = treePath, + Sha = createdBlob.Sha, + }); + + var createdTree = await _gitHubClient.GitDatabase.Tree.Create(Helper.UserName, repoName, newTree); + var treeSha = createdTree.Sha; + + // Creating a commit + var parent = await _gitHubClient.GitDatabase.Reference.Get(Helper.UserName, repoName, reference); + var commit = new NewCommit(commitMessage, treeSha, parent.Object.Sha); + + var createdCommit = await _gitHubClient.GitDatabase.Commit.Create(Helper.UserName, repoName, commit); + await _gitHubClient.GitDatabase.Reference.Update(Helper.UserName, repoName, reference, new ReferenceUpdate(createdCommit.Sha)); + + return createdCommit; + } + + class PullRequestData + { + public int Number { get; set; } + public string Sha { get; set; } + } +} diff --git a/Octokit.Tests.Integration/Clients/RepositoryDeployKeysClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryDeployKeysClientTests.cs index e52dd5c2..5a70ae4f 100644 --- a/Octokit.Tests.Integration/Clients/RepositoryDeployKeysClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoryDeployKeysClientTests.cs @@ -11,7 +11,6 @@ public class RepositoryDeployKeysClientTests : IDisposable readonly IGitHubClient _client; IRepositoryDeployKeysClient _fixture; Repository _repository; - DeployKey _deployKey; string _owner; public RepositoryDeployKeysClientTests() diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 6ab0b6e6..824137cb 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -82,6 +82,7 @@ + diff --git a/Octokit.Tests.Integration/Reactive/ObservableRespositoryDeployKeysClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableRespositoryDeployKeysClientTests.cs index 03fe1d56..eaf7e367 100644 --- a/Octokit.Tests.Integration/Reactive/ObservableRespositoryDeployKeysClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/ObservableRespositoryDeployKeysClientTests.cs @@ -13,7 +13,6 @@ public class ObservableRespositoryDeployKeysClientTests : IDisposable const string _keyTitle = "octokit@github"; ObservableRepositoryDeployKeysClient _client; Repository _repository; - DeployKey _deployKey; string _owner; public ObservableRespositoryDeployKeysClientTests() diff --git a/Octokit.Tests/Clients/PullRequestReviewCommentsClientTests.cs b/Octokit.Tests/Clients/PullRequestReviewCommentsClientTests.cs new file mode 100644 index 00000000..70d38c37 --- /dev/null +++ b/Octokit.Tests/Clients/PullRequestReviewCommentsClientTests.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NSubstitute; +using Octokit; +using Octokit.Tests.Helpers; +using Xunit; + +public class PullRequestReviewCommentsClientTests +{ + public class TheModelConstructors + { + [Fact] + public void PullRequestReviewCommentCreateEnsuresArgumentsValue() + { + string body = "body"; + string commitId = "sha"; + string path = "path"; + int position = 1; + + var comment = new PullRequestReviewCommentCreate(body, commitId, path, position); + + Assert.Equal(body, comment.Body); + Assert.Equal(commitId, comment.CommitId); + Assert.Equal(path, comment.Path); + Assert.Equal(position, comment.Position); + } + + [Fact] + public void PullRequestReviewCommentCreateEnsuresArgumentsNotNull() + { + string body = "body"; + string commitId = "sha"; + string path = "path"; + int position = 1; + + Assert.Throws(() => new PullRequestReviewCommentCreate(null, commitId, path, position)); + Assert.Throws(() => new PullRequestReviewCommentCreate("", commitId, path, position)); + Assert.Throws(() => new PullRequestReviewCommentCreate(body, null, path, position)); + Assert.Throws(() => new PullRequestReviewCommentCreate(body, "", path, position)); + Assert.Throws(() => new PullRequestReviewCommentCreate(body, commitId, null, position)); + Assert.Throws(() => new PullRequestReviewCommentCreate(body, commitId, "", position)); + } + + [Fact] + public void PullRequestReviewCommentEditEnsuresArgumentsValue() + { + string body = "body"; + + var comment = new PullRequestReviewCommentEdit(body); + + Assert.Equal(body, comment.Body); + } + + [Fact] + public void PullRequestReviewCommentEditEnsuresArgumentsNotNull() + { + Assert.Throws(() => new PullRequestReviewCommentEdit(null)); + Assert.Throws(() => new PullRequestReviewCommentEdit("")); + } + + [Fact] + public void PullRequestReviewCommentReplyCreateEnsuresArgumentsValue() + { + string body = "body"; + int inReplyTo = 1; + + var comment = new PullRequestReviewCommentReplyCreate(body, inReplyTo); + + Assert.Equal(body, comment.Body); + Assert.Equal(inReplyTo, comment.InReplyTo); + } + + [Fact] + public void PullRequestReviewCommentReplyCreateEnsuresArgumentsNotNull() + { + int inReplyTo = 1; + + Assert.Throws(() => new PullRequestReviewCommentReplyCreate(null, inReplyTo)); + Assert.Throws(() => new PullRequestReviewCommentReplyCreate("", inReplyTo)); + } + } + + public class TheGetForPullRequestMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + client.GetAll("fakeOwner", "fakeRepoName", 7); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/7/comments")); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + await AssertEx.Throws(async () => await client.GetAll(null, "name", 1)); + await AssertEx.Throws(async () => await client.GetAll("", "name", 1)); + await AssertEx.Throws(async () => await client.GetAll("owner", null, 1)); + await AssertEx.Throws(async () => await client.GetAll("owner", "", 1)); + } + } + + public class TheGetForRepositoryMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + var request = new PullRequestReviewCommentRequest + { + Direction = SortDirection.Descending, + Since = new DateTimeOffset(2013, 11, 15, 11, 43, 01, 00, new TimeSpan()), + Sort = PullRequestReviewCommentSort.Updated, + }; + + client.GetForRepository("fakeOwner", "fakeRepoName", request); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/comments"), + Arg.Is>(d => d.Count == 3 + && d["direction"] == "desc" + && d["since"] == "2013-11-15T11:43:01Z" + && d["sort"] == "updated")); + } + + [Fact] + public void RequestsCorrectUrlWithoutSelectedSortingArguments() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + client.GetForRepository("fakeOwner", "fakeRepoName"); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/comments"), + Arg.Is>(d => d.Count == 2 + && d["direction"] == "asc" + && d["sort"] == "created")); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var client = new PullRequestReviewCommentsClient(Substitute.For()); + + var request = new PullRequestReviewCommentRequest(); + + await AssertEx.Throws(async () => await client.GetForRepository(null, "name", request)); + await AssertEx.Throws(async () => await client.GetForRepository("", "name", request)); + await AssertEx.Throws(async () => await client.GetForRepository("owner", null, request)); + await AssertEx.Throws(async () => await client.GetForRepository("owner", "", request)); + await AssertEx.Throws(async () => await client.GetForRepository("owner", "name", null)); + } + + [Fact] + public async Task EnsuresDefaultValues() + { + var request = new PullRequestReviewCommentRequest(); + + Assert.Equal(SortDirection.Ascending, request.Direction); + Assert.Null(request.Since); + Assert.Equal(PullRequestReviewCommentSort.Created, request.Sort); + } + } + + public class TheGetCommentMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + client.GetComment("fakeOwner", "fakeRepoName", 53); + + connection.Received().Get(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/comments/53"), + null); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var client = new PullRequestReviewCommentsClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.GetComment(null, "name", 1)); + await AssertEx.Throws(async () => await client.GetComment("", "name", 1)); + await AssertEx.Throws(async () => await client.GetComment("owner", null, 1)); + await AssertEx.Throws(async () => await client.GetComment("owner", "", 1)); + } + } + + public class TheCreateMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + var comment = new PullRequestReviewCommentCreate("Comment content", "qe3dsdsf6", "file.css", 7); + + client.Create("fakeOwner", "fakeRepoName", 13, comment); + + connection.Connection.Received().Post(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/13/comments"), + comment, null, null); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + string body = "Comment content"; + string commitId = "qe3dsdsf6"; + string path = "file.css"; + int position = 7; + + var comment = new PullRequestReviewCommentCreate(body, commitId, path, position); + + await AssertEx.Throws(async () => await client.Create(null, "fakeRepoName", 1, comment)); + await AssertEx.Throws(async () => await client.Create("", "fakeRepoName", 1, comment)); + await AssertEx.Throws(async () => await client.Create("fakeOwner", null, 1, comment)); + await AssertEx.Throws(async () => await client.Create("fakeOwner", "", 1, comment)); + await AssertEx.Throws(async () => await client.Create("fakeOwner", "fakeRepoName", 1, null)); + } + } + + public class TheCreateReplyMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + var comment = new PullRequestReviewCommentReplyCreate("Comment content", 5); + + client.CreateReply("fakeOwner", "fakeRepoName", 13, comment); + + connection.Connection.Received().Post(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/13/comments"), + comment, null, null); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + string body = "Comment content"; + int inReplyTo = 7; + + var comment = new PullRequestReviewCommentReplyCreate(body, inReplyTo); + + await AssertEx.Throws(async () => await client.CreateReply(null, "fakeRepoName", 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("", "fakeRepoName", 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("fakeOwner", null, 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("fakeOwner", "", 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("fakeOwner", "fakeRepoName", 1, null)); + } + } + + public class TheEditMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + var comment = new PullRequestReviewCommentEdit("New comment content"); + + client.Edit("fakeOwner", "fakeRepoName", 13, comment); + + connection.Received().Patch(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/comments/13"), comment); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + var body = "New comment content"; + + var comment = new PullRequestReviewCommentEdit(body); + + await AssertEx.Throws(async () => await client.Edit(null, "fakeRepoName", 1, comment)); + await AssertEx.Throws(async () => await client.Edit("", "fakeRepoName", 1, comment)); + await AssertEx.Throws(async () => await client.Edit("fakeOwner", null, 1, comment)); + await AssertEx.Throws(async () => await client.Edit("fakeOwner", "", 1, comment)); + await AssertEx.Throws(async () => await client.Edit("fakeOwner", null, 1, null)); + } + } + + public class TheDeleteMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + client.Delete("fakeOwner", "fakeRepoName", 13); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "repos/fakeOwner/fakeRepoName/pulls/comments/13")); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var connection = Substitute.For(); + var client = new PullRequestReviewCommentsClient(connection); + + await AssertEx.Throws(async () => await client.Delete(null, "fakeRepoName", 1)); + await AssertEx.Throws(async () => await client.Delete("", "fakeRepoName", 1)); + await AssertEx.Throws(async () => await client.Delete("fakeOwner", null, 1)); + await AssertEx.Throws(async () => await client.Delete("fakeOwner", "", 1)); + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index effdec53..c3477890 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -80,6 +80,7 @@ + @@ -158,6 +159,7 @@ + diff --git a/Octokit.Tests/Reactive/ObservablePullRequestReviewCommentsClientTests.cs b/Octokit.Tests/Reactive/ObservablePullRequestReviewCommentsClientTests.cs new file mode 100644 index 00000000..5c7f2d93 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservablePullRequestReviewCommentsClientTests.cs @@ -0,0 +1,400 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Threading.Tasks; +using NSubstitute; +using Octokit.Internal; +using Octokit.Reactive; +using Octokit.Tests.Helpers; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservablePullRequestReviewCommentsClientTests + { + static ApiInfo CreateApiInfo(IDictionary links) + { + return new ApiInfo(links, new List(), new List(), "etag", new RateLimit(new Dictionary())); + } + + public class TheGetForPullRequestMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var firstPageUrl = new Uri("repos/fakeOwner/fakeRepoName/pulls/7/comments", UriKind.Relative); + var secondPageUrl = new Uri("https://example.com/page/2"); + var firstPageLinks = new Dictionary { { "next", secondPageUrl } }; + var firstPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 1}, + new PullRequestReviewComment {Id = 2}, + new PullRequestReviewComment {Id = 3} + }, + ApiInfo = CreateApiInfo(firstPageLinks) + }; + var thirdPageUrl = new Uri("https://example.com/page/3"); + var secondPageLinks = new Dictionary { { "next", thirdPageUrl } }; + var secondPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 4}, + new PullRequestReviewComment {Id = 5}, + new PullRequestReviewComment {Id = 6} + }, + ApiInfo = CreateApiInfo(secondPageLinks) + }; + var lastPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 7} + }, + ApiInfo = CreateApiInfo(new Dictionary()) + }; + + var gitHubClient = Substitute.For(); + gitHubClient.Connection.Get>(firstPageUrl, null, null) + .Returns(Task.Factory.StartNew>>(() => firstPageResponse)); + gitHubClient.Connection.Get>(secondPageUrl, null, null) + .Returns(Task.Factory.StartNew>>(() => secondPageResponse)); + gitHubClient.Connection.Get>(thirdPageUrl, null, null) + .Returns(Task.Factory.StartNew>>(() => lastPageResponse)); + + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + var results = await client.GetForPullRequest("fakeOwner", "fakeRepoName", 7).ToArray(); + + Assert.Equal(7, results.Length); + gitHubClient.Connection.Received(1).Get>(firstPageUrl, null, null); + gitHubClient.Connection.Received(1).Get>(secondPageUrl, null, null); + gitHubClient.Connection.Received(1).Get>(thirdPageUrl, null, null); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + await AssertEx.Throws(async () => await client.GetForPullRequest(null, "name", 1)); + await AssertEx.Throws(async () => await client.GetForPullRequest("", "name", 1)); + await AssertEx.Throws(async () => await client.GetForPullRequest("owner", null, 1)); + await AssertEx.Throws(async () => await client.GetForPullRequest("owner", "", 1)); + await AssertEx.Throws(async () => await client.GetForPullRequest(null, null, 1)); + await AssertEx.Throws(async () => await client.GetForPullRequest("", "", 1)); + } + } + + public class TheGetForRepositoryMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var firstPageUrl = new Uri("repos/fakeOwner/fakeRepoName/pulls/comments", UriKind.Relative); + var secondPageUrl = new Uri("https://example.com/page/2"); + var firstPageLinks = new Dictionary { { "next", secondPageUrl } }; + var firstPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 1}, + new PullRequestReviewComment {Id = 2}, + new PullRequestReviewComment {Id = 3} + }, + ApiInfo = CreateApiInfo(firstPageLinks) + }; + var thirdPageUrl = new Uri("https://example.com/page/3"); + var secondPageLinks = new Dictionary { { "next", thirdPageUrl } }; + var secondPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 4}, + new PullRequestReviewComment {Id = 5}, + new PullRequestReviewComment {Id = 6} + }, + ApiInfo = CreateApiInfo(secondPageLinks) + }; + var lastPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 7}, + new PullRequestReviewComment {Id = 8}, + }, + ApiInfo = CreateApiInfo(new Dictionary()) + }; + + var gitHubClient = Substitute.For(); + + gitHubClient.Connection.Get>(firstPageUrl, + Arg.Is>(d => d.Count == 3 + && d["direction"] == "desc" + && d["since"] == "2013-11-15T11:43:01Z" + && d["sort"] == "updated"), null) + .Returns(Task.Factory.StartNew>>(() => firstPageResponse)); + gitHubClient.Connection.Get>(secondPageUrl, null, null) + .Returns(Task.Factory.StartNew>>(() => secondPageResponse)); + gitHubClient.Connection.Get>(thirdPageUrl, null, null) + .Returns(Task.Factory.StartNew>>(() => lastPageResponse)); + + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + var request = new PullRequestReviewCommentRequest + { + Direction = SortDirection.Descending, + Since = new DateTimeOffset(2013, 11, 15, 11, 43, 01, 00, new TimeSpan()), + Sort = PullRequestReviewCommentSort.Updated, + }; + + var results = await client.GetForRepository("fakeOwner", "fakeRepoName", request).ToArray(); + + Assert.Equal(8, results.Length); + gitHubClient.Connection.Received(1).Get>(firstPageUrl, + Arg.Is>(d => d.Count == 3 + && d["direction"] == "desc" + && d["since"] == "2013-11-15T11:43:01Z" + && d["sort"] == "updated"), null); + gitHubClient.Connection.Received(1).Get>(secondPageUrl, null, null); + gitHubClient.Connection.Received(1).Get>(thirdPageUrl, null, null); + } + + [Fact] + public async Task RequestsCorrectUrlWithoutSelectedSortingArguments() + { + var firstPageUrl = new Uri("repos/fakeOwner/fakeRepoName/pulls/comments", UriKind.Relative); + var secondPageUrl = new Uri("https://example.com/page/2"); + var firstPageLinks = new Dictionary { { "next", secondPageUrl } }; + var firstPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 1}, + new PullRequestReviewComment {Id = 2}, + new PullRequestReviewComment {Id = 3} + }, + ApiInfo = CreateApiInfo(firstPageLinks) + }; + var thirdPageUrl = new Uri("https://example.com/page/3"); + var secondPageLinks = new Dictionary { { "next", thirdPageUrl } }; + var secondPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 4}, + new PullRequestReviewComment {Id = 5}, + new PullRequestReviewComment {Id = 6} + }, + ApiInfo = CreateApiInfo(secondPageLinks) + }; + var lastPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + new PullRequestReviewComment {Id = 7}, + new PullRequestReviewComment {Id = 8}, + }, + ApiInfo = CreateApiInfo(new Dictionary()) + }; + + var gitHubClient = Substitute.For(); + + gitHubClient.Connection.Get>(firstPageUrl, + Arg.Is>(d => d.Count == 2 + && d["direction"] == "asc" + && d["sort"] == "created"), null) + .Returns(Task.Factory.StartNew>>(() => firstPageResponse)); + gitHubClient.Connection.Get>(secondPageUrl, null, null) + .Returns(Task.Factory.StartNew>>(() => secondPageResponse)); + gitHubClient.Connection.Get>(thirdPageUrl, null, null) + .Returns(Task.Factory.StartNew>>(() => lastPageResponse)); + + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + var results = await client.GetForRepository("fakeOwner", "fakeRepoName").ToArray(); + + Assert.Equal(8, results.Length); + gitHubClient.Connection.Received(1).Get>(firstPageUrl, + Arg.Is>(d => d.Count == 2 + && d["direction"] == "asc" + && d["sort"] == "created"), null); + gitHubClient.Connection.Received(1).Get>(secondPageUrl, null, null); + gitHubClient.Connection.Received(1).Get>(thirdPageUrl, null, null); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var client = new ObservablePullRequestReviewCommentsClient(Substitute.For()); + + var request = new PullRequestReviewCommentRequest(); + + await AssertEx.Throws(async () => await client.GetForRepository(null, "name", request)); + await AssertEx.Throws(async () => await client.GetForRepository("", "name", request)); + await AssertEx.Throws(async () => await client.GetForRepository("owner", null, request)); + await AssertEx.Throws(async () => await client.GetForRepository("owner", "", request)); + await AssertEx.Throws(async () => await client.GetForRepository("owner", "name", null)); + } + } + + public class TheGetCommentMethod + { + [Fact] + public void GetsFromClientPullRequestComment() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + client.GetComment("fakeOwner", "fakeRepoName", 53); + + gitHubClient.PullRequest.Comment.Received().GetComment("fakeOwner", "fakeRepoName", 53); + } + + [Fact] + public async Task EnsuresArgumentsNonNull() + { + var client = new ObservablePullRequestReviewCommentsClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.GetComment(null, "name", 1)); + await AssertEx.Throws(async () => await client.GetComment("", "name", 1)); + await AssertEx.Throws(async () => await client.GetComment("owner", null, 1)); + await AssertEx.Throws(async () => await client.GetComment("owner", "", 1)); + await AssertEx.Throws(async () => await client.GetComment(null, null, 1)); + await AssertEx.Throws(async () => await client.GetComment("", "", 1)); + } + } + + public class TheCreateMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + var comment = new PullRequestReviewCommentCreate("Comment content", "qe3dsdsf6", "file.css", 7); + + client.Create("fakeOwner", "fakeRepoName", 13, comment); + + gitHubClient.PullRequest.Comment.Received().Create("fakeOwner", "fakeRepoName", 13, comment); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + string body = "Comment content"; + string commitId = "qe3dsdsf6"; + string path = "file.css"; + int position = 7; + + var comment = new PullRequestReviewCommentCreate(body, commitId, path, position); + + await AssertEx.Throws(async () => await client.Create(null, "name", 1, comment)); + await AssertEx.Throws(async () => await client.Create("", "name", 1, comment)); + await AssertEx.Throws(async () => await client.Create("owner", null, 1, comment)); + await AssertEx.Throws(async () => await client.Create("owner", "", 1, comment)); + await AssertEx.Throws(async () => await client.Create("owner", "name", 1, null)); + } + } + + public class TheCreateReplyMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + var comment = new PullRequestReviewCommentReplyCreate("Comment content", 9); + + client.CreateReply("fakeOwner", "fakeRepoName", 13, comment); + + gitHubClient.PullRequest.Comment.Received().CreateReply("fakeOwner", "fakeRepoName", 13, comment); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + string body = "Comment content"; + int inReplyTo = 7; + + var comment = new PullRequestReviewCommentReplyCreate(body, inReplyTo); + + await AssertEx.Throws(async () => await client.CreateReply(null, "name", 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("", "name", 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("owner", null, 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("owner", "", 1, comment)); + await AssertEx.Throws(async () => await client.CreateReply("owner", "name", 1, null)); + } + } + + public class TheEditMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + var comment = new PullRequestReviewCommentEdit("New comment content"); + + client.Edit("fakeOwner", "fakeRepoName", 13, comment); + + gitHubClient.PullRequest.Comment.Received().Edit("fakeOwner", "fakeRepoName", 13, comment); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + var body = "New comment content"; + + var comment = new PullRequestReviewCommentEdit(body); + + await AssertEx.Throws(async () => await client.Edit(null, "name", 1, comment)); + await AssertEx.Throws(async () => await client.Edit("", "name", 1, comment)); + await AssertEx.Throws(async () => await client.Edit("owner", null, 1, comment)); + await AssertEx.Throws(async () => await client.Edit("owner", "", 1, comment)); + await AssertEx.Throws(async () => await client.Edit("owner", "name", 1, null)); + } + } + + public class TheDeleteMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + client.Delete("fakeOwner", "fakeRepoName", 13); + + gitHubClient.PullRequest.Comment.Received().Delete("fakeOwner", "fakeRepoName", 13); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservablePullRequestReviewCommentsClient(gitHubClient); + + await AssertEx.Throws(async () => await client.Delete(null, "name", 1)); + await AssertEx.Throws(async () => await client.Delete("", "name", 1)); + await AssertEx.Throws(async () => await client.Delete("owner", null, 1)); + await AssertEx.Throws(async () => await client.Delete("owner", "", 1)); + } + } + } +} diff --git a/Octokit/Clients/IPullRequestReviewCommentsClient.cs b/Octokit/Clients/IPullRequestReviewCommentsClient.cs new file mode 100644 index 00000000..e749effe --- /dev/null +++ b/Octokit/Clients/IPullRequestReviewCommentsClient.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + public interface IPullRequestReviewCommentsClient + { + /// + /// Gets review comments for a specified pull request. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The list of s for the specified pull request + Task> GetAll(string owner, string name, int number); + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The list of s for the specified repository + Task> GetForRepository(string owner, string name); + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The sorting parameters + /// The list of s for the specified repository + Task> GetForRepository(string owner, string name, PullRequestReviewCommentRequest request); + + /// + /// Gets a single pull request review comment by number. + /// + /// http://developer.github.com/v3/pulls/comments/#get-a-single-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The + Task GetComment(string owner, string name, int number); + + /// + /// Creates a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The Pull Request number + /// The comment + /// The created + Task Create(string owner, string name, int number, PullRequestReviewCommentCreate comment); + + /// + /// Creates a comment on a pull request review as a reply to another comment. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The comment + /// The created + Task CreateReply(string owner, string name, int number, PullRequestReviewCommentReplyCreate comment); + + /// + /// Edits a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#edit-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The edited comment + /// The edited + Task Edit(string owner, string name, int number, PullRequestReviewCommentEdit comment); + + /// + /// Deletes a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#delete-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// + Task Delete(string owner, string name, int number); + } +} diff --git a/Octokit/Clients/IPullRequestsClient.cs b/Octokit/Clients/IPullRequestsClient.cs index 0c0b5fc6..940db5c0 100644 --- a/Octokit/Clients/IPullRequestsClient.cs +++ b/Octokit/Clients/IPullRequestsClient.cs @@ -7,6 +7,10 @@ namespace Octokit public interface IPullRequestsClient { /// + /// Client for managing comments. + /// + IPullRequestReviewCommentsClient Comment { get; } + /// Get a pull request by number. /// /// diff --git a/Octokit/Clients/PullRequestReviewCommentsClient.cs b/Octokit/Clients/PullRequestReviewCommentsClient.cs new file mode 100644 index 00000000..fee2143c --- /dev/null +++ b/Octokit/Clients/PullRequestReviewCommentsClient.cs @@ -0,0 +1,159 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Octokit +{ + public class PullRequestReviewCommentsClient : ApiClient, IPullRequestReviewCommentsClient + { + public PullRequestReviewCommentsClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// Gets review comments for a specified pull request. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The list of s for the specified pull request + public Task> GetAll(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return ApiConnection.GetAll(ApiUrls.PullRequestReviewComments(owner, name, number)); + } + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The list of s for the specified repository + public Task> GetForRepository(string owner, string name) + { + return GetForRepository(owner, name, new PullRequestReviewCommentRequest()); + } + + /// + /// Gets a list of the pull request review comments in a specified repository. + /// + /// http://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + /// The owner of the repository + /// The name of the repository + /// The sorting parameters + /// The list of s for the specified repository + public Task> GetForRepository(string owner, string name, PullRequestReviewCommentRequest request) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(request, "request"); + + return ApiConnection.GetAll(ApiUrls.PullRequestReviewCommentsRepository(owner, name), request.ToParametersDictionary()); + } + + /// + /// Gets a single pull request review comment by number. + /// + /// http://developer.github.com/v3/pulls/comments/#get-a-single-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The + public Task GetComment(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return ApiConnection.Get(ApiUrls.PullRequestReviewComment(owner, name, number)); + } + + /// + /// Creates a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The Pull Request number + /// The comment + /// The created + public async Task Create(string owner, string name, int number, PullRequestReviewCommentCreate comment) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(comment, "comment"); + + var response = await ApiConnection.Connection.Post(ApiUrls.PullRequestReviewComments(owner, name, number), comment, null, null).ConfigureAwait(false); + + if (response.StatusCode != HttpStatusCode.Created) + { + throw new ApiException("Invalid Status Code returned. Expected a 201", response.StatusCode); + } + + return response.BodyAsObject; + } + + /// + /// Creates a comment on a pull request review as a reply to another comment. + /// + /// http://developer.github.com/v3/pulls/comments/#create-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The comment + /// The created + public async Task CreateReply(string owner, string name, int number, PullRequestReviewCommentReplyCreate comment) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(comment, "comment"); + + var response = await ApiConnection.Connection.Post(ApiUrls.PullRequestReviewComments(owner, name, number), comment, null, null).ConfigureAwait(false); + + if (response.StatusCode != HttpStatusCode.Created) + { + throw new ApiException("Invalid Status Code returned. Expected a 201", response.StatusCode); + } + + return response.BodyAsObject; + } + + /// + /// Edits a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#edit-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// The edited comment + /// The edited + public Task Edit(string owner, string name, int number, PullRequestReviewCommentEdit comment) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(comment, "comment"); + + return ApiConnection.Patch(ApiUrls.PullRequestReviewComment(owner, name, number), comment); + } + + /// + /// Deletes a comment on a pull request review. + /// + /// http://developer.github.com/v3/pulls/comments/#delete-a-comment + /// The owner of the repository + /// The name of the repository + /// The pull request review comment number + /// + public Task Delete(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return ApiConnection.Delete(ApiUrls.PullRequestReviewComment(owner, name, number)); + } + } +} diff --git a/Octokit/Clients/PullRequestsClient.cs b/Octokit/Clients/PullRequestsClient.cs index 1c1919fb..bf2a026c 100644 --- a/Octokit/Clients/PullRequestsClient.cs +++ b/Octokit/Clients/PullRequestsClient.cs @@ -6,12 +6,16 @@ namespace Octokit { public class PullRequestsClient : ApiClient, IPullRequestsClient { - public PullRequestsClient(IApiConnection apiConnection) - : base(apiConnection) + public PullRequestsClient(IApiConnection apiConnection) : base(apiConnection) { + Comment = new PullRequestReviewCommentsClient(apiConnection); } /// + /// Client for managing comments. + /// + public IPullRequestReviewCommentsClient Comment { get; private set; } + /// Get a pull request by number. /// /// diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs index 97a663db..4580642a 100644 --- a/Octokit/GitHubClient.cs +++ b/Octokit/GitHubClient.cs @@ -86,6 +86,7 @@ namespace Octokit Notification = new NotificationsClient(apiConnection); Oauth = new OauthClient(connection); Organization = new OrganizationsClient(apiConnection); + PullRequest = new PullRequestsClient(apiConnection); Repository = new RepositoriesClient(apiConnection); Gist = new GistsClient(apiConnection); Release = new ReleasesClient(apiConnection); @@ -136,6 +137,7 @@ namespace Octokit public IMiscellaneousClient Miscellaneous { get; private set; } public IOauthClient Oauth { get; private set; } public IOrganizationsClient Organization { get; private set; } + public IPullRequestsClient PullRequest { get; private set; } public IRepositoriesClient Repository { get; private set; } public IGistsClient Gist { get; private set; } public IReleasesClient Release { get; private set; } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 44dc39e2..980dfe11 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -862,6 +862,41 @@ namespace Octokit return "users/{0}/events/orgs/{1}".FormatUri(user, organization); } + /// + /// Returns the for the comments of a specified pull request review. + /// + /// The owner of the repository + /// The name of the repository + /// The pull request number + /// The + public static Uri PullRequestReviewComments(string owner, string name, int number) + { + return "repos/{0}/{1}/pulls/{2}/comments".FormatUri(owner, name, number); + } + + /// + /// Returns the for the specified pull request review comment. + /// + /// The owner of the repository + /// The name of the repository + /// The comment number + /// The + public static Uri PullRequestReviewComment(string owner, string name, int number) + { + return "repos/{0}/{1}/pulls/comments/{2}".FormatUri(owner, name, number); + } + + /// + /// Returns the for the pull request review comments on a specified repository. + /// + /// The owner of the repository + /// The name of the repository + /// The + public static Uri PullRequestReviewCommentsRepository(string owner, string name) + { + return "repos/{0}/{1}/pulls/comments".FormatUri(owner, name); + } + /// /// Returns the for a specifc blob. /// diff --git a/Octokit/IGitHubClient.cs b/Octokit/IGitHubClient.cs index ba5da490..54472d5f 100644 --- a/Octokit/IGitHubClient.cs +++ b/Octokit/IGitHubClient.cs @@ -15,6 +15,7 @@ namespace Octokit IMiscellaneousClient Miscellaneous { get; } IOauthClient Oauth { get; } IOrganizationsClient Organization { get; } + IPullRequestsClient PullRequest { get; } IRepositoriesClient Repository { get; } IGistsClient Gist { get; } IReleasesClient Release { get; } diff --git a/Octokit/Models/Request/PullRequestReviewCommentCreate.cs b/Octokit/Models/Request/PullRequestReviewCommentCreate.cs new file mode 100644 index 00000000..d9ae04b8 --- /dev/null +++ b/Octokit/Models/Request/PullRequestReviewCommentCreate.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class PullRequestReviewCommentCreate : RequestParameters + { + /// + /// Creates a comment + /// + /// The text of the comment + /// The SHA of the commit to comment on + /// The relative path of the file to comment on + /// The line index in the diff to comment on + public PullRequestReviewCommentCreate(string body, string commitId, string path, int position) + { + Ensure.ArgumentNotNullOrEmptyString(body, "body"); + Ensure.ArgumentNotNullOrEmptyString(commitId, "commitId"); + Ensure.ArgumentNotNullOrEmptyString(path, "path"); + + Body = body; + CommitId = commitId; + Path = path; + Position = position; + } + + /// + /// The text of the comment. + /// + public string Body { get; private set; } + + /// + /// The SHA of the commit to comment on. + /// + public string CommitId { get; private set; } + + /// + /// The relative path of the file to comment on. + /// + public string Path { get; private set; } + + /// + /// The line index in the diff to comment on. + /// + public int Position { get; private set; } + } +} diff --git a/Octokit/Models/Request/PullRequestReviewCommentEdit.cs b/Octokit/Models/Request/PullRequestReviewCommentEdit.cs new file mode 100644 index 00000000..f8cc7ba1 --- /dev/null +++ b/Octokit/Models/Request/PullRequestReviewCommentEdit.cs @@ -0,0 +1,24 @@ +using System.Diagnostics; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class PullRequestReviewCommentEdit : RequestParameters + { + /// + /// Creates an edit to a comment + /// + /// The text of the comment + public PullRequestReviewCommentEdit(string body) + { + Ensure.ArgumentNotNullOrEmptyString(body, "body"); + + Body = body; + } + + /// + /// The text of the comment. + /// + public string Body { get; private set; } + } +} diff --git a/Octokit/Models/Request/PullRequestReviewCommentReplyCreate.cs b/Octokit/Models/Request/PullRequestReviewCommentReplyCreate.cs new file mode 100644 index 00000000..221ea677 --- /dev/null +++ b/Octokit/Models/Request/PullRequestReviewCommentReplyCreate.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class PullRequestReviewCommentReplyCreate : RequestParameters + { + /// + /// Creates a comment that is replying to another comment. + /// + /// The text of the comment + /// The comment Id to reply to + public PullRequestReviewCommentReplyCreate(string body, int inReplyTo) + { + Ensure.ArgumentNotNullOrEmptyString(body, "body"); + + Body = body; + InReplyTo = inReplyTo; + } + + /// + /// The text of the comment. + /// + public string Body { get; private set; } + + /// + /// The comment Id to reply to. + /// + public int InReplyTo { get; private set; } + } +} diff --git a/Octokit/Models/Request/PullRequestReviewCommentRequest.cs b/Octokit/Models/Request/PullRequestReviewCommentRequest.cs new file mode 100644 index 00000000..4bd22d0f --- /dev/null +++ b/Octokit/Models/Request/PullRequestReviewCommentRequest.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class PullRequestReviewCommentRequest : RequestParameters + { + public PullRequestReviewCommentRequest() + { + // Default arguments + Sort = PullRequestReviewCommentSort.Created; + Direction = SortDirection.Ascending; + Since = null; + } + + /// + /// Can be either created or updated. Default: created. + /// + public PullRequestReviewCommentSort Sort { get; set; } + + /// + /// Can be either asc or desc. Default: asc. + /// + public SortDirection Direction { get; set; } + + /// + /// Only comments updated at or after this time are returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. + /// + public DateTimeOffset? Since { get; set; } + } +} diff --git a/Octokit/Models/Response/PullRequestReviewComment.cs b/Octokit/Models/Response/PullRequestReviewComment.cs new file mode 100644 index 00000000..03e6b418 --- /dev/null +++ b/Octokit/Models/Response/PullRequestReviewComment.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class PullRequestReviewComment + { + /// + /// URL of the comment via the API. + /// + public Uri Url { get; set; } + + /// + /// The comment Id. + /// + public int Id { get; set; } + + /// + /// The diff hunk the comment is about. + /// + public string DiffHunk { get; set; } + + /// + /// The relative path of the file the comment is about. + /// + public string Path { get; set; } + + /// + /// The line index in the diff. + /// + public int? Position { get; set; } + + /// + /// The comment original position. + /// + public int? OriginalPosition { get; set; } + + /// + /// The commit Id the comment is associated with. + /// + public string CommitId { get; set; } + + /// + /// The original commit Id the comment is associated with. + /// + public string OriginalCommitId { get; set; } + + /// + /// The user that created the comment. + /// + public User User { get; set; } + + /// + /// The text of the comment. + /// + public string Body { get; set; } + + /// + /// The date the comment was created. + /// + public DateTimeOffset CreatedAt { get; set; } + + /// + /// The date the comment was last updated. + /// + public DateTimeOffset UpdatedAt { get; set; } + + /// + /// The URL for this comment on Github.com + /// + public Uri HtmlUrl { get; set; } + + /// + /// The URL for the pull request via the API. + /// + public Uri PullRequestUrl { get; set; } + } + + public enum PullRequestReviewCommentSort + { + /// + /// Sort by create date (default) + /// + Created, + + /// + /// Sort by the date of the last update + /// + Updated, + } +} diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index cf135ed5..f7483d98 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -154,6 +154,7 @@ + @@ -261,6 +262,12 @@ + + + + + + diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index bb4b6d1f..7fd056f7 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -241,6 +241,13 @@ + + + + + + + diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 09c2666f..882e3807 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -236,6 +236,13 @@ + + + + + + + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 7cf66edd..69b78da2 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -317,6 +317,13 @@ + + + + + + + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 8ab09384..acf45b5a 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -65,6 +65,10 @@ + + + + @@ -85,7 +89,6 @@ - @@ -106,7 +109,6 @@ - @@ -258,6 +260,11 @@ + + + + + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 46051948..43853cc5 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -96,6 +96,14 @@ + + + + + + + + @@ -152,6 +160,7 @@ + @@ -181,7 +190,6 @@ - @@ -190,7 +198,6 @@ -