Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Lock/Unlock Functionality for Issues #1185

Merged
merged 8 commits into from
Apr 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shiftkey
Why is this function showing changes.
I had opened a seperate PR to change it which has been merged.
The function has changed also here

Had to remove it to remove conflicts.

}

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space in between uri and object

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be handled by the next FormatCode call, so let's 👢 addressing this...

{
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