diff --git a/Octokit.Reactive/Clients/IObservableIssuesClient.cs b/Octokit.Reactive/Clients/IObservableIssuesClient.cs index 008d9955b0..d9ca53a334 100644 --- a/Octokit.Reactive/Clients/IObservableIssuesClient.cs +++ b/Octokit.Reactive/Clients/IObservableIssuesClient.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Reactive; namespace Octokit.Reactive { @@ -156,5 +157,25 @@ public interface IObservableIssuesClient /// /// IObservable Update(string owner, string name, int number, IssueUpdate issueUpdate); + + /// + /// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue. + /// + /// https://developer.github.com/v3/issues/#lock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + IObservable Lock(string owner, string name, int number); + + /// + /// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue. + /// + /// https://developer.github.com/v3/issues/#unlock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + IObservable Unlock(string owner, string name, int number); } -} +} \ No newline at end of file diff --git a/Octokit.Reactive/Clients/ObservableIssuesClient.cs b/Octokit.Reactive/Clients/ObservableIssuesClient.cs index 4571696334..007b743db4 100644 --- a/Octokit.Reactive/Clients/ObservableIssuesClient.cs +++ b/Octokit.Reactive/Clients/ObservableIssuesClient.cs @@ -1,6 +1,7 @@ using System; using System.Reactive.Threading.Tasks; using Octokit.Reactive.Internal; +using System.Reactive; namespace Octokit.Reactive { @@ -222,5 +223,37 @@ public IObservable Update(string owner, string name, int number, IssueUpd return _client.Update(owner, name, number, issueUpdate).ToObservable(); } + + /// + /// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue. + /// + /// https://developer.github.com/v3/issues/#lock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + public IObservable Lock(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _client.Lock(owner, name, number).ToObservable(); + } + + /// + /// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue. + /// + /// https://developer.github.com/v3/issues/#unlock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + public IObservable Unlock(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _client.Unlock(owner, name, number).ToObservable(); + } } } diff --git a/Octokit.Tests.Integration/Clients/IssuesClientTests.cs b/Octokit.Tests.Integration/Clients/IssuesClientTests.cs index 3dd7be567f..c9619a4bef 100644 --- a/Octokit.Tests.Integration/Clients/IssuesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/IssuesClientTests.cs @@ -61,6 +61,24 @@ public async Task CanCreateRetrieveAndCloseIssue() } [IntegrationTest] + public async Task CanLockAndUnlockIssue() + { + var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" }; + var issue = await _issuesClient.Create(_context.RepositoryOwner, _context.RepositoryName, newIssue); + Assert.False(issue.Locked); + + await _issuesClient.Lock(_context.RepositoryOwner, _context.RepositoryName, issue.Number); + var retrieved = await _issuesClient.Get(_context.RepositoryOwner, _context.RepositoryName, issue.Number); + Assert.NotNull(retrieved); + Assert.True(retrieved.Locked); + + await _issuesClient.Unlock(_context.RepositoryOwner, _context.RepositoryName, issue.Number); + retrieved = await _issuesClient.Get(_context.RepositoryOwner, _context.RepositoryName, issue.Number); + Assert.NotNull(retrieved); + Assert.False(retrieved.Locked); + } + + [IntegrationTest] public async Task CanListOpenIssuesWithDefaultSort() { var newIssue1 = new NewIssue("A test issue1") { Body = "A new unassigned issue" }; diff --git a/Octokit.Tests.Integration/Reactive/ObservableIssuesClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableIssuesClientTests.cs index 5eb94af756..9c6acf9885 100644 --- a/Octokit.Tests.Integration/Reactive/ObservableIssuesClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/ObservableIssuesClientTests.cs @@ -72,6 +72,23 @@ public async Task CanCreateAndUpdateIssues() Assert.Equal("Modified integration test issue", updateResult.Title); } + [IntegrationTest] + public async Task CanLockAndUnlockIssues() + { + var newIssue = new NewIssue("Integration Test Issue"); + + var createResult = await _client.Create(_context.RepositoryOwner, _context.RepositoryName, newIssue); + Assert.False(createResult.Locked); + + await _client.Lock(_context.RepositoryOwner, _context.RepositoryName, createResult.Number); + var lockResult = await _client.Get(_context.RepositoryOwner, _context.RepositoryName, createResult.Number); + Assert.True(lockResult.Locked); + + await _client.Unlock(_context.RepositoryOwner, _context.RepositoryName, createResult.Number); + var unlockIssueResult = await _client.Get(_context.RepositoryOwner, _context.RepositoryName, createResult.Number); + Assert.False(unlockIssueResult.Locked); + } + public void Dispose() { _context.Dispose(); diff --git a/Octokit.Tests/Clients/IssuesClientTests.cs b/Octokit.Tests/Clients/IssuesClientTests.cs index 2f3b759b19..bcb14395f9 100644 --- a/Octokit.Tests/Clients/IssuesClientTests.cs +++ b/Octokit.Tests/Clients/IssuesClientTests.cs @@ -185,6 +185,58 @@ public async Task EnsuresArgumentsNotNull() } } + public class TheLockMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssuesClient(connection); + + client.Lock("fake", "repo", 42); + + connection.Received().Put(Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/lock"), Arg.Any(), Arg.Any(), Arg.Is(u => u.ToString() == "application/vnd.github.the-key-preview+json")); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var connection = Substitute.For(); + var client = new IssuesClient(connection); + + await Assert.ThrowsAsync(() => client.Lock(null, "name", 1)); + await Assert.ThrowsAsync(() => client.Lock("", "name", 1)); + await Assert.ThrowsAsync(() => client.Lock("owner", null, 1)); + await Assert.ThrowsAsync(() => client.Lock("owner", "", 1)); + } + } + + public class TheUnlockMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssuesClient(connection); + + client.Unlock("fake", "repo", 42); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/lock"), Arg.Any(), Arg.Is(u => u.ToString() == "application/vnd.github.the-key-preview+json")); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var connection = Substitute.For(); + var client = new IssuesClient(connection); + + await Assert.ThrowsAsync(() => client.Unlock(null, "name", 1)); + await Assert.ThrowsAsync(() => client.Unlock("", "name", 1)); + await Assert.ThrowsAsync(() => client.Unlock("owner", null, 1)); + await Assert.ThrowsAsync(() => client.Unlock("owner", "", 1)); + } + } + public class TheCtor { [Fact] diff --git a/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs b/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs index b09a498a69..c511b90175 100644 --- a/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs @@ -337,6 +337,56 @@ public void EnsuresArgumentsNotNull() } } + public class TheLockMethod + { + [Fact] + public void LocksIssue() + { + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + client.Lock("fake", "repo", 42); + gitHubClient.Issue.Received().Lock("fake", "repo", 42); + } + + [Fact] + public void EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + Assert.Throws(() => client.Lock(null, "name", 42)); + Assert.Throws(() => client.Lock("", "name", 42)); + Assert.Throws(() => client.Lock("owner", null, 42)); + Assert.Throws(() => client.Lock("owner", "", 42)); + } + } + + public class TheUnlockMethod + { + [Fact] + public void UnlocksIssue() + { + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + client.Unlock("fake", "repo", 42); + gitHubClient.Issue.Received().Unlock("fake", "repo", 42); + } + + [Fact] + public void EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + Assert.Throws(() => client.Unlock(null, "name", 42)); + Assert.Throws(() => client.Unlock("", "name", 42)); + Assert.Throws(() => client.Unlock("owner", null, 42)); + Assert.Throws(() => client.Unlock("owner", "", 42)); + } + } + public class TheCtor { [Fact] diff --git a/Octokit/Clients/IIssuesClient.cs b/Octokit/Clients/IIssuesClient.cs index bc0e362cb9..bea894bfdf 100644 --- a/Octokit/Clients/IIssuesClient.cs +++ b/Octokit/Clients/IIssuesClient.cs @@ -152,10 +152,10 @@ public interface IIssuesClient Task Create(string owner, string name, NewIssue newIssue); /// - /// Creates an issue for the specified repository. Any user with pull access to a repository can create an + /// Updates an issue for the specified repository. Any user with pull access to a repository can update an /// issue. /// - /// http://developer.github.com/v3/issues/#create-an-issue + /// http://developer.github.com/v3/issues/#edit-an-issue /// The owner of the repository /// The name of the repository /// The issue number @@ -163,5 +163,25 @@ public interface IIssuesClient /// /// Task Update(string owner, string name, int number, IssueUpdate issueUpdate); + + /// + /// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue. + /// + /// https://developer.github.com/v3/issues/#lock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + Task Lock(string owner, string name, int number); + + /// + /// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue. + /// + /// https://developer.github.com/v3/issues/#unlock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + Task Unlock(string owner, string name, int number); } } diff --git a/Octokit/Clients/IssuesClient.cs b/Octokit/Clients/IssuesClient.cs index 8b924245bb..4757c01403 100644 --- a/Octokit/Clients/IssuesClient.cs +++ b/Octokit/Clients/IssuesClient.cs @@ -225,5 +225,37 @@ public Task Update(string owner, string name, int number, IssueUpdate iss return ApiConnection.Patch(ApiUrls.Issue(owner, name, number), issueUpdate); } + + /// + /// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue. + /// + /// https://developer.github.com/v3/issues/#lock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + public Task Lock(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return ApiConnection.Put(ApiUrls.IssueLock(owner, name, number), new object(), null, AcceptHeaders.IssueLockingUnlockingApiPreview); + } + + /// + /// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue. + /// + /// https://developer.github.com/v3/issues/#unlock-an-issue + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + public Task Unlock(string owner, string name, int number) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return ApiConnection.Delete(ApiUrls.IssueLock(owner, name, number), new object(), AcceptHeaders.IssueLockingUnlockingApiPreview); + } } } diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index 21c0047eb5..bf3423b340 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -14,6 +14,8 @@ public static class AcceptHeaders public const string StarCreationTimestamps = "application/vnd.github.v3.star+json"; + public const string IssueLockingUnlockingApiPreview = "application/vnd.github.the-key-preview+json"; + public const string CommitReferenceSha1Preview = "application/vnd.github.chitauri-preview+sha"; } } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 54d6edaa27..179f013b57 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -294,6 +294,18 @@ public static Uri Issue(string owner, string name, int number) return "repos/{0}/{1}/issues/{2}".FormatUri(owner, name, number); } + /// + /// Returns the for the specified issue to be locked/unlocked. + /// + /// The owner of the repository + /// The name of the repository + /// The issue number + /// + public static Uri IssueLock(string owner, string name, int number) + { + return "repos/{0}/{1}/issues/{2}/lock".FormatUri(owner, name, number); + } + /// /// Returns the for the comments for all issues in a specific repo. /// diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 0cf57496e0..a08a8aebd6 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -467,6 +467,21 @@ public Task Delete(Uri uri, object data) return Connection.Delete(uri, data); } + /// + /// Performs an asynchronous HTTP DELETE request that expects an empty response. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// Specifies accept response media type + /// The returned + public Task Delete(Uri uri, object data, string accepts) + { + Ensure.ArgumentNotNull(uri, "uri"); + Ensure.ArgumentNotNull(data, "data"); + Ensure.ArgumentNotNull(accepts, "accepts"); + + return Connection.Delete(uri, data, accepts); + } /// /// Executes a GET to the API object at the specified URI. This operation is appropriate for /// API calls which wants to return the redirect URL. diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 81a1f86bce..dc5bccf2bc 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -484,6 +484,22 @@ public async Task Delete(Uri uri, object data) return response.HttpResponse.StatusCode; } + /// + /// Performs an asynchronous HTTP DELETE request that expects an empty response. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// Specifies accept response media type + /// The returned + public async Task Delete(Uri uri,object data, string accepts) + { + Ensure.ArgumentNotNull(uri, "uri"); + Ensure.ArgumentNotNull(accepts, "accepts"); + + var response = await SendData(uri, HttpMethod.Delete, data, accepts, null, CancellationToken.None); + return response.HttpResponse.StatusCode; + } + /// /// Base address for the connection. /// diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 81966a101e..f3fe9f20db 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -293,6 +293,15 @@ public interface IApiConnection /// A for the request's execution. Task Delete(Uri uri, object data); + /// + /// Performs an asynchronous HTTP DELETE request that expects an empty response. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// Specifies accept response media type + /// The returned + Task Delete(Uri uri, object data, string accepts); + /// /// Executes a GET to the API object at the specified URI. This operation is appropriate for /// API calls which wants to return the redirect URL. diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 137f488ee2..74b0ab0a60 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -234,6 +234,15 @@ public interface IConnection : IApiInfoProvider /// The returned Task Delete(Uri uri, object data); + /// + /// Performs an asynchronous HTTP DELETE request that expects an empty response. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// Specifies accept response media type + /// The returned + Task Delete(Uri uri, object data, string accepts); + /// /// Base address for the connection. /// diff --git a/SolutionInfo.cs b/SolutionInfo.cs index 22509412fb..a52961be74 100644 --- a/SolutionInfo.cs +++ b/SolutionInfo.cs @@ -6,10 +6,8 @@ [assembly: AssemblyVersionAttribute("0.19.0")] [assembly: AssemblyFileVersionAttribute("0.19.0")] [assembly: ComVisibleAttribute(false)] -namespace System -{ - internal static class AssemblyVersionInformation - { +namespace System { + internal static class AssemblyVersionInformation { internal const string Version = "0.19.0"; } }