Skip to content

Commit

Permalink
Merge pull request #1185 from prayankmathur/issue896
Browse files Browse the repository at this point in the history
Implemented Lock/Unlock Functionality for Issues
  • Loading branch information
shiftkey committed Apr 5, 2016
2 parents 5ec98b0 + 7324039 commit e8df990
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 7 deletions.
23 changes: 22 additions & 1 deletion Octokit.Reactive/Clients/IObservableIssuesClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive;

namespace Octokit.Reactive
{
Expand Down Expand Up @@ -156,5 +157,25 @@ public interface IObservableIssuesClient
/// </param>
/// <returns></returns>
IObservable<Issue> Update(string owner, string name, int number, IssueUpdate issueUpdate);

/// <summary>
/// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#lock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
IObservable<Unit> Lock(string owner, string name, int number);

/// <summary>
/// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#unlock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
IObservable<Unit> Unlock(string owner, string name, int number);
}
}
}
33 changes: 33 additions & 0 deletions Octokit.Reactive/Clients/ObservableIssuesClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Reactive.Threading.Tasks;
using Octokit.Reactive.Internal;
using System.Reactive;

namespace Octokit.Reactive
{
Expand Down Expand Up @@ -222,5 +223,37 @@ public IObservable<Issue> Update(string owner, string name, int number, IssueUpd

return _client.Update(owner, name, number, issueUpdate).ToObservable();
}

/// <summary>
/// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#lock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
public IObservable<Unit> Lock(string owner, string name, int number)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");

return _client.Lock(owner, name, number).ToObservable();
}

/// <summary>
/// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#unlock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
public IObservable<Unit> Unlock(string owner, string name, int number)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");

return _client.Unlock(owner, name, number).ToObservable();
}
}
}
18 changes: 18 additions & 0 deletions Octokit.Tests.Integration/Clients/IssuesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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" };
Expand Down
17 changes: 17 additions & 0 deletions Octokit.Tests.Integration/Reactive/ObservableIssuesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
52 changes: 52 additions & 0 deletions Octokit.Tests/Clients/IssuesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,58 @@ public async Task EnsuresArgumentsNotNull()
}
}

public class TheLockMethod
{
[Fact]
public void PostsToCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);

client.Lock("fake", "repo", 42);

connection.Received().Put<Issue>(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/issues/42/lock"), Arg.Any<object>(), Arg.Any<string>(), Arg.Is<string>(u => u.ToString() == "application/vnd.github.the-key-preview+json"));
}

[Fact]
public async Task EnsuresArgumentsNotNull()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);

await Assert.ThrowsAsync<ArgumentNullException>(() => client.Lock(null, "name", 1));
await Assert.ThrowsAsync<ArgumentException>(() => client.Lock("", "name", 1));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.Lock("owner", null, 1));
await Assert.ThrowsAsync<ArgumentException>(() => client.Lock("owner", "", 1));
}
}

public class TheUnlockMethod
{
[Fact]
public void PostsToCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);

client.Unlock("fake", "repo", 42);

connection.Received().Delete(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/issues/42/lock"), Arg.Any<object>(), Arg.Is<string>(u => u.ToString() == "application/vnd.github.the-key-preview+json"));
}

[Fact]
public async Task EnsuresArgumentsNotNull()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);

await Assert.ThrowsAsync<ArgumentNullException>(() => client.Unlock(null, "name", 1));
await Assert.ThrowsAsync<ArgumentException>(() => client.Unlock("", "name", 1));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.Unlock("owner", null, 1));
await Assert.ThrowsAsync<ArgumentException>(() => client.Unlock("owner", "", 1));
}
}

public class TheCtor
{
[Fact]
Expand Down
50 changes: 50 additions & 0 deletions Octokit.Tests/Reactive/ObservableIssuesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,56 @@ public void EnsuresArgumentsNotNull()
}
}

public class TheLockMethod
{
[Fact]
public void LocksIssue()
{
var gitHubClient = Substitute.For<IGitHubClient>();
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<IGitHubClient>();
var client = new ObservableIssuesClient(gitHubClient);

Assert.Throws<ArgumentNullException>(() => client.Lock(null, "name", 42));
Assert.Throws<ArgumentException>(() => client.Lock("", "name", 42));
Assert.Throws<ArgumentNullException>(() => client.Lock("owner", null, 42));
Assert.Throws<ArgumentException>(() => client.Lock("owner", "", 42));
}
}

public class TheUnlockMethod
{
[Fact]
public void UnlocksIssue()
{
var gitHubClient = Substitute.For<IGitHubClient>();
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<IGitHubClient>();
var client = new ObservableIssuesClient(gitHubClient);

Assert.Throws<ArgumentNullException>(() => client.Unlock(null, "name", 42));
Assert.Throws<ArgumentException>(() => client.Unlock("", "name", 42));
Assert.Throws<ArgumentNullException>(() => client.Unlock("owner", null, 42));
Assert.Throws<ArgumentException>(() => client.Unlock("owner", "", 42));
}
}

public class TheCtor
{
[Fact]
Expand Down
24 changes: 22 additions & 2 deletions Octokit/Clients/IIssuesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,36 @@ public interface IIssuesClient
Task<Issue> Create(string owner, string name, NewIssue newIssue);

/// <summary>
/// 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.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <remarks>http://developer.github.com/v3/issues/#edit-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <param name="issueUpdate">An <see cref="IssueUpdate"/> instance describing the changes to make to the issue
/// </param>
/// <returns></returns>
Task<Issue> Update(string owner, string name, int number, IssueUpdate issueUpdate);

/// <summary>
/// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#lock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
Task Lock(string owner, string name, int number);

/// <summary>
/// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#unlock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
Task Unlock(string owner, string name, int number);
}
}
32 changes: 32 additions & 0 deletions Octokit/Clients/IssuesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,37 @@ public Task<Issue> Update(string owner, string name, int number, IssueUpdate iss

return ApiConnection.Patch<Issue>(ApiUrls.Issue(owner, name, number), issueUpdate);
}

/// <summary>
/// Locks an issue for the specified repository. Issue owners and users with push access can lock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#lock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
public Task Lock(string owner, string name, int number)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");

return ApiConnection.Put<Issue>(ApiUrls.IssueLock(owner, name, number), new object(), null, AcceptHeaders.IssueLockingUnlockingApiPreview);
}

/// <summary>
/// Unlocks an issue for the specified repository. Issue owners and users with push access can unlock an issue.
/// </summary>
/// <remarks>https://developer.github.com/v3/issues/#unlock-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
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);
}
}
}
2 changes: 2 additions & 0 deletions Octokit/Helpers/AcceptHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
12 changes: 12 additions & 0 deletions Octokit/Helpers/ApiUrls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,18 @@ public static Uri Issue(string owner, string name, int number)
return "repos/{0}/{1}/issues/{2}".FormatUri(owner, name, number);
}

/// <summary>
/// Returns the <see cref="Uri"/> for the specified issue to be locked/unlocked.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
public static Uri IssueLock(string owner, string name, int number)
{
return "repos/{0}/{1}/issues/{2}/lock".FormatUri(owner, name, number);
}

/// <summary>
/// Returns the <see cref="Uri"/> for the comments for all issues in a specific repo.
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions Octokit/Http/ApiConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,21 @@ public Task Delete(Uri uri, object data)
return Connection.Delete(uri, data);
}

/// <summary>
/// Performs an asynchronous HTTP DELETE request that expects an empty response.
/// </summary>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="data">The object to serialize as the body of the request</param>
/// <param name="accepts">Specifies accept response media type</param>
/// <returns>The returned <seealso cref="HttpStatusCode"/></returns>
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);
}
/// <summary>
/// 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.
Expand Down
16 changes: 16 additions & 0 deletions Octokit/Http/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,22 @@ public async Task<HttpStatusCode> Delete(Uri uri, object data)
return response.HttpResponse.StatusCode;
}

/// <summary>
/// Performs an asynchronous HTTP DELETE request that expects an empty response.
/// </summary>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="data">The object to serialize as the body of the request</param>
/// <param name="accepts">Specifies accept response media type</param>
/// <returns>The returned <seealso cref="HttpStatusCode"/></returns>
public async Task<HttpStatusCode> Delete(Uri uri,object data, string accepts)
{
Ensure.ArgumentNotNull(uri, "uri");
Ensure.ArgumentNotNull(accepts, "accepts");

var response = await SendData<object>(uri, HttpMethod.Delete, data, accepts, null, CancellationToken.None);
return response.HttpResponse.StatusCode;
}

/// <summary>
/// Base address for the connection.
/// </summary>
Expand Down
Loading

0 comments on commit e8df990

Please sign in to comment.