From f3d4b751cc7c255748c972a19b76467f0a50f39f Mon Sep 17 00:00:00 2001 From: Haacked Date: Fri, 2 Jan 2015 16:14:56 -0800 Subject: [PATCH] Make Issue readonly and fix a bug Found a bug in which we were using Name when we should be using Login. --- Octokit.Tests/Models/IssueTest.cs | 267 +++++++ Octokit.Tests/OctoKit.Tests-NetCore45.csproj | 1 + Octokit.Tests/Octokit.Tests-Portable.csproj | 1 + Octokit.Tests/Octokit.Tests.csproj | 1 + .../Reactive/ObservableIssuesClientTests.cs | 667 +++++++++--------- Octokit/Models/Request/IssueUpdate.cs | 15 +- Octokit/Models/Response/Issue.cs | 38 +- 7 files changed, 631 insertions(+), 359 deletions(-) create mode 100644 Octokit.Tests/Models/IssueTest.cs diff --git a/Octokit.Tests/Models/IssueTest.cs b/Octokit.Tests/Models/IssueTest.cs new file mode 100644 index 00000000..ed3626df --- /dev/null +++ b/Octokit.Tests/Models/IssueTest.cs @@ -0,0 +1,267 @@ +using System.Linq; +using Octokit; +using Octokit.Internal; +using Xunit; + +public class IssueTest +{ + [Fact] + public void CanBeDeserialized() + { + const string json = @"{ +""id"": 1, +""url"": ""https://api.github.com/repos/octocat/Hello-World/issues/1347"", +""html_url"": ""https://github.com/octocat/Hello-World/issues/1347"", +""number"": 1347, +""state"": ""open"", +""title"": ""Found a bug"", +""body"": ""I'm having a problem with this."", +""user"": { +""login"": ""octocat"", +""id"": 1, +""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", +""gravatar_id"": """", +""url"": ""https://api.github.com/users/octocat"", +""html_url"": ""https://github.com/octocat"", +""followers_url"": ""https://api.github.com/users/octocat/followers"", +""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", +""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", +""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", +""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", +""organizations_url"": ""https://api.github.com/users/octocat/orgs"", +""repos_url"": ""https://api.github.com/users/octocat/repos"", +""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", +""received_events_url"": ""https://api.github.com/users/octocat/received_events"", +""type"": ""User"", +""site_admin"": false +}, +""labels"": [ +{ + ""url"": ""https://api.github.com/repos/octocat/Hello-World/labels/bug"", + ""name"": ""bug"", + ""color"": ""f29513"" +} +], +""assignee"": { +""login"": ""octocat"", +""id"": 1, +""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", +""gravatar_id"": """", +""url"": ""https://api.github.com/users/octocat"", +""html_url"": ""https://github.com/octocat"", +""followers_url"": ""https://api.github.com/users/octocat/followers"", +""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", +""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", +""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", +""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", +""organizations_url"": ""https://api.github.com/users/octocat/orgs"", +""repos_url"": ""https://api.github.com/users/octocat/repos"", +""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", +""received_events_url"": ""https://api.github.com/users/octocat/received_events"", +""type"": ""User"", +""site_admin"": false +}, +""milestone"": { +""url"": ""https://api.github.com/repos/octocat/Hello-World/milestones/1"", +""number"": 1, +""state"": ""open"", +""title"": ""v1.0"", +""description"": """", +""creator"": { + ""login"": ""octocat"", + ""id"": 1, + ""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", + ""gravatar_id"": """", + ""url"": ""https://api.github.com/users/octocat"", + ""html_url"": ""https://github.com/octocat"", + ""followers_url"": ""https://api.github.com/users/octocat/followers"", + ""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", + ""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", + ""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", + ""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", + ""organizations_url"": ""https://api.github.com/users/octocat/orgs"", + ""repos_url"": ""https://api.github.com/users/octocat/repos"", + ""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", + ""received_events_url"": ""https://api.github.com/users/octocat/received_events"", + ""type"": ""User"", + ""site_admin"": false +}, +""open_issues"": 4, +""closed_issues"": 8, +""created_at"": ""2011-04-10T20:09:31Z"", +""updated_at"": ""2014-03-03T18:58:10Z"", +""closed_at"": ""2013-02-12T13:22:01Z"", +""due_on"": null +}, +""comments"": 0, +""pull_request"": { +""url"": ""https://api.github.com/repos/octocat/Hello-World/pulls/1347"", +""html_url"": ""https://github.com/octocat/Hello-World/pull/1347"", +""diff_url"": ""https://github.com/octocat/Hello-World/pull/1347.diff"", +""patch_url"": ""https://github.com/octocat/Hello-World/pull/1347.patch"" +}, +""closed_at"": null, +""created_at"": ""2011-04-22T13:33:48Z"", +""updated_at"": ""2011-04-22T13:33:48Z"", +""closed_by"": { +""login"": ""octocat"", +""id"": 1, +""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", +""gravatar_id"": """", +""url"": ""https://api.github.com/users/octocat"", +""html_url"": ""https://github.com/octocat"", +""followers_url"": ""https://api.github.com/users/octocat/followers"", +""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", +""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", +""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", +""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", +""organizations_url"": ""https://api.github.com/users/octocat/orgs"", +""repos_url"": ""https://api.github.com/users/octocat/repos"", +""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", +""received_events_url"": ""https://api.github.com/users/octocat/received_events"", +""type"": ""User"", +""site_admin"": false +} +}"; + var serializer = new SimpleJsonSerializer(); + + var issue = serializer.Deserialize(json); + + Assert.Equal(1347, issue.Number); + Assert.Equal("octocat", issue.User.Login); + Assert.Equal("bug", issue.Labels.First().Name); + } + + public class TheToUpdateMethod + { + [Fact] + public void CreatesAnIssueUpdateRequestObject() + { + const string json = @"{ +""id"": 1, +""url"": ""https://api.github.com/repos/octocat/Hello-World/issues/1347"", +""html_url"": ""https://github.com/octocat/Hello-World/issues/1347"", +""number"": 1347, +""state"": ""open"", +""title"": ""Found a bug"", +""body"": ""I'm having a problem with this."", +""user"": { +""login"": ""octocat"", +""id"": 1, +""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", +""gravatar_id"": """", +""url"": ""https://api.github.com/users/octocat"", +""html_url"": ""https://github.com/octocat"", +""followers_url"": ""https://api.github.com/users/octocat/followers"", +""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", +""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", +""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", +""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", +""organizations_url"": ""https://api.github.com/users/octocat/orgs"", +""repos_url"": ""https://api.github.com/users/octocat/repos"", +""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", +""received_events_url"": ""https://api.github.com/users/octocat/received_events"", +""type"": ""User"", +""site_admin"": false +}, +""labels"": [ +{ + ""url"": ""https://api.github.com/repos/octocat/Hello-World/labels/bug"", + ""name"": ""bug"", + ""color"": ""f29513"" +} +], +""assignee"": { +""login"": ""octocat"", +""id"": 1, +""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", +""gravatar_id"": """", +""url"": ""https://api.github.com/users/octocat"", +""html_url"": ""https://github.com/octocat"", +""followers_url"": ""https://api.github.com/users/octocat/followers"", +""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", +""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", +""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", +""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", +""organizations_url"": ""https://api.github.com/users/octocat/orgs"", +""repos_url"": ""https://api.github.com/users/octocat/repos"", +""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", +""received_events_url"": ""https://api.github.com/users/octocat/received_events"", +""type"": ""User"", +""site_admin"": false +}, +""milestone"": { +""url"": ""https://api.github.com/repos/octocat/Hello-World/milestones/1"", +""number"": 1, +""state"": ""open"", +""title"": ""v1.0"", +""description"": """", +""creator"": { + ""login"": ""octocat"", + ""id"": 1, + ""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", + ""gravatar_id"": """", + ""url"": ""https://api.github.com/users/octocat"", + ""html_url"": ""https://github.com/octocat"", + ""followers_url"": ""https://api.github.com/users/octocat/followers"", + ""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", + ""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", + ""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", + ""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", + ""organizations_url"": ""https://api.github.com/users/octocat/orgs"", + ""repos_url"": ""https://api.github.com/users/octocat/repos"", + ""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", + ""received_events_url"": ""https://api.github.com/users/octocat/received_events"", + ""type"": ""User"", + ""site_admin"": false +}, +""open_issues"": 4, +""closed_issues"": 8, +""created_at"": ""2011-04-10T20:09:31Z"", +""updated_at"": ""2014-03-03T18:58:10Z"", +""closed_at"": ""2013-02-12T13:22:01Z"", +""due_on"": null +}, +""comments"": 0, +""pull_request"": { +""url"": ""https://api.github.com/repos/octocat/Hello-World/pulls/1347"", +""html_url"": ""https://github.com/octocat/Hello-World/pull/1347"", +""diff_url"": ""https://github.com/octocat/Hello-World/pull/1347.diff"", +""patch_url"": ""https://github.com/octocat/Hello-World/pull/1347.patch"" +}, +""closed_at"": null, +""created_at"": ""2011-04-22T13:33:48Z"", +""updated_at"": ""2011-04-22T13:33:48Z"", +""closed_by"": { +""login"": ""octocat"", +""id"": 1, +""avatar_url"": ""https://github.com/images/error/octocat_happy.gif"", +""gravatar_id"": """", +""url"": ""https://api.github.com/users/octocat"", +""html_url"": ""https://github.com/octocat"", +""followers_url"": ""https://api.github.com/users/octocat/followers"", +""following_url"": ""https://api.github.com/users/octocat/following{/other_user}"", +""gists_url"": ""https://api.github.com/users/octocat/gists{/gist_id}"", +""starred_url"": ""https://api.github.com/users/octocat/starred{/owner}{/repo}"", +""subscriptions_url"": ""https://api.github.com/users/octocat/subscriptions"", +""organizations_url"": ""https://api.github.com/users/octocat/orgs"", +""repos_url"": ""https://api.github.com/users/octocat/repos"", +""events_url"": ""https://api.github.com/users/octocat/events{/privacy}"", +""received_events_url"": ""https://api.github.com/users/octocat/received_events"", +""type"": ""User"", +""site_admin"": false +} +}"; + var serializer = new SimpleJsonSerializer(); + var issue = serializer.Deserialize(json); + + var update = issue.ToUpdate(); + + Assert.Equal("bug", update.Labels.Single()); + Assert.Equal(1, update.Milestone.GetValueOrDefault()); + Assert.Equal("octocat", update.Assignee); + } + } +} + + diff --git a/Octokit.Tests/OctoKit.Tests-NetCore45.csproj b/Octokit.Tests/OctoKit.Tests-NetCore45.csproj index fa1a6e12..4cfe3b38 100644 --- a/Octokit.Tests/OctoKit.Tests-NetCore45.csproj +++ b/Octokit.Tests/OctoKit.Tests-NetCore45.csproj @@ -126,6 +126,7 @@ + diff --git a/Octokit.Tests/Octokit.Tests-Portable.csproj b/Octokit.Tests/Octokit.Tests-Portable.csproj index 3fac46f5..0eb60263 100644 --- a/Octokit.Tests/Octokit.Tests-Portable.csproj +++ b/Octokit.Tests/Octokit.Tests-Portable.csproj @@ -126,6 +126,7 @@ + diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 1753a385..b532af3a 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -140,6 +140,7 @@ + diff --git a/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs b/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs index 9f010265..f2363815 100644 --- a/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableIssuesClientTests.cs @@ -9,344 +9,349 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Xunit; - public class ObservableIssuesClientTests +public class ObservableIssuesClientTests +{ + public class TheGetMethod { - public class TheGetMethod + [Fact] + public void GetsFromClientIssueIssue() { - [Fact] - public void GetsFromClientIssueIssue() - { - var gitHubClient = Substitute.For(); - var client = new ObservableIssuesClient(gitHubClient); + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); - client.Get("fake", "repo", 42); + client.Get("fake", "repo", 42); - gitHubClient.Issue.Received().Get("fake", "repo", 42); - } - - [Fact] - public async Task EnsuresNonNullArguments() - { - var client = new ObservableIssuesClient(Substitute.For()); - - await AssertEx.Throws(async () => await client.Get(null, "name", 1)); - await AssertEx.Throws(async () => await client.Get("owner", null, 1)); - await AssertEx.Throws(async () => await client.Get(null, "", 1)); - await AssertEx.Throws(async () => await client.Get("", null, 1)); - } + gitHubClient.Issue.Received().Get("fake", "repo", 42); } - public class TheGetForRepositoryMethod + [Fact] + public async Task EnsuresNonNullArguments() { - [Fact] - public async Task ReturnsEveryPageOfIssues() - { - var firstPageUrl = new Uri("repos/fake/repo/issues", 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 Issue {Number = 1}, - new Issue {Number = 2}, - new Issue {Number = 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 Issue {Number = 4}, - new Issue {Number = 5}, - new Issue {Number = 6}, - }, - ApiInfo = CreateApiInfo(secondPageLinks) - }; - var lastPageResponse = new ApiResponse> - { - BodyAsObject = new List - { - new Issue {Number = 7}, - }, - ApiInfo = CreateApiInfo(new Dictionary()) - }; - var gitHubClient = Substitute.For(); - gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), - Arg.Is>(d => d.Count == 4 - && d["direction"] == "desc" - && d["state"] == "open" - && d["sort"] == "created" - && d["filter"] == "assigned"), Arg.Any()) - .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 ObservableIssuesClient(gitHubClient); + var client = new ObservableIssuesClient(Substitute.For()); - var results = await client.GetForRepository("fake", "repo").ToArray(); - - Assert.Equal(7, results.Length); - Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); - Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); - Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); - } - } - - public class TheGetAllForOwnedAndMemberRepositoriesMethod - { - [Fact] - public async Task ReturnsEveryPageOfIssues() - { - var firstPageUrl = new Uri("user/issues", 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 Issue {Number = 1}, - new Issue {Number = 2}, - new Issue {Number = 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 Issue {Number = 4}, - new Issue {Number = 5}, - new Issue {Number = 6}, - }, - ApiInfo = CreateApiInfo(secondPageLinks) - }; - var lastPageResponse = new ApiResponse> - { - BodyAsObject = new List - { - new Issue {Number = 7}, - }, - ApiInfo = CreateApiInfo(new Dictionary()) - }; - var gitHubClient = Substitute.For(); - gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), - Arg.Is>(d => d.Count == 4 - && d["direction"] == "desc" - && d["state"] == "open" - && d["sort"] == "created" - && d["filter"] == "assigned"), Arg.Any()) - .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 ObservableIssuesClient(gitHubClient); - - var results = await client.GetAllForOwnedAndMemberRepositories().ToArray(); - - Assert.Equal(7, results.Length); - Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); - Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); - Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); - } - } - - public class TheGetAllForOrganizationMethod - { - [Fact] - public async Task ReturnsEveryPageOfIssues() - { - var firstPageUrl = new Uri("orgs/test/issues", 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 Issue {Number = 1}, - new Issue {Number = 2}, - new Issue {Number = 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 Issue {Number = 4}, - new Issue {Number = 5}, - new Issue {Number = 6}, - }, - ApiInfo = CreateApiInfo(secondPageLinks) - }; - var lastPageResponse = new ApiResponse> - { - BodyAsObject = new List - { - new Issue {Number = 7}, - }, - ApiInfo = CreateApiInfo(new Dictionary()) - }; - var gitHubClient = Substitute.For(); - gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), - Arg.Is>(d => d.Count == 4 - && d["direction"] == "desc" - && d["state"] == "open" - && d["sort"] == "created" - && d["filter"] == "assigned"), Arg.Any()) - .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 ObservableIssuesClient(gitHubClient); - - var results = await client.GetAllForOrganization("test").ToArray(); - - Assert.Equal(7, results.Length); - Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); - Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); - Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); - } - } - - public class TheGetAllForCurrentMethod - { - [Fact] - public async Task ReturnsEveryPageOfIssues() - { - var firstPageUrl = new Uri("issues", 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 Issue {Number = 1}, - new Issue {Number = 2}, - new Issue {Number = 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 Issue {Number = 4}, - new Issue {Number = 5}, - new Issue {Number = 6}, - }, - ApiInfo = CreateApiInfo(secondPageLinks) - }; - var lastPageResponse = new ApiResponse> - { - BodyAsObject = new List - { - new Issue {Number = 7}, - }, - ApiInfo = CreateApiInfo(new Dictionary()) - }; - var gitHubClient = Substitute.For(); - gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), - Arg.Is>(d => d.Count == 4 - && d["direction"] == "desc" - && d["state"] == "open" - && d["sort"] == "created" - && d["filter"] == "assigned"), Arg.Any()) - .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 ObservableIssuesClient(gitHubClient); - - var results = await client.GetAllForCurrent().ToArray(); - - Assert.Equal(7, results.Length); - Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); - Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); - Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); - } - } - - public class TheCreateMethod - { - [Fact] - public void CreatesFromClientIssueIssue() - { - var newIssue = new NewIssue("some title"); - var gitHubClient = Substitute.For(); - var client = new ObservableIssuesClient(gitHubClient); - - client.Create("fake", "repo", newIssue); - - gitHubClient.Issue.Received().Create("fake", "repo", newIssue); - } - - [Fact] - public void EnsuresArgumentsNotNull() - { - var gitHubClient = Substitute.For(); - var client = new ObservableIssuesClient(gitHubClient); - - Assert.Throws(() => client.Create(null, "name", new NewIssue("title"))); - Assert.Throws(() => client.Create("", "name", new NewIssue("x"))); - Assert.Throws(() => client.Create("owner", null, new NewIssue("x"))); - Assert.Throws(() => client.Create("owner", "", new NewIssue("x"))); - Assert.Throws(() => client.Create("owner", "name", null)); - } - } - - public class TheUpdateMethod - { - [Fact] - public void UpdatesClientIssueIssue() - { - var issueUpdate = new IssueUpdate(); - var gitHubClient = Substitute.For(); - var client = new ObservableIssuesClient(gitHubClient); - - client.Update("fake", "repo", 42, issueUpdate); - - gitHubClient.Issue.Received().Update("fake", "repo", 42, issueUpdate); - } - - [Fact] - public void EnsuresArgumentsNotNull() - { - var gitHubClient = Substitute.For(); - var client = new ObservableIssuesClient(gitHubClient); - - Assert.Throws(() => client.Create(null, "name", new NewIssue("title"))); - Assert.Throws(() => client.Create("", "name", new NewIssue("x"))); - Assert.Throws(() => client.Create("owner", null, new NewIssue("x"))); - Assert.Throws(() => client.Create("owner", "", new NewIssue("x"))); - Assert.Throws(() => client.Create("owner", "name", null)); - } - } - - public class TheCtor - { - [Fact] - public void EnsuresArgument() - { - Assert.Throws(() => new IssuesClient(null)); - } - } - - static ApiInfo CreateApiInfo(IDictionary links) - { - return new ApiInfo(links, new List(), new List(), "etag", new RateLimit(new Dictionary())); + await AssertEx.Throws(async () => await client.Get(null, "name", 1)); + await AssertEx.Throws(async () => await client.Get("owner", null, 1)); + await AssertEx.Throws(async () => await client.Get(null, "", 1)); + await AssertEx.Throws(async () => await client.Get("", null, 1)); } } + + public class TheGetForRepositoryMethod + { + [Fact] + public async Task ReturnsEveryPageOfIssues() + { + var firstPageUrl = new Uri("repos/fake/repo/issues", UriKind.Relative); + var secondPageUrl = new Uri("https://example.com/page/2"); + var firstPageLinks = new Dictionary { { "next", secondPageUrl } }; + var firstPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(1), + CreateIssue(2), + CreateIssue(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 + { + CreateIssue(4), + CreateIssue(5), + CreateIssue(6) + }, + ApiInfo = CreateApiInfo(secondPageLinks) + }; + var lastPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(7), + }, + ApiInfo = CreateApiInfo(new Dictionary()) + }; + var gitHubClient = Substitute.For(); + gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), + Arg.Is>(d => d.Count == 4 + && d["direction"] == "desc" + && d["state"] == "open" + && d["sort"] == "created" + && d["filter"] == "assigned"), Arg.Any()) + .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 ObservableIssuesClient(gitHubClient); + + var results = await client.GetForRepository("fake", "repo").ToArray(); + + Assert.Equal(7, results.Length); + Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); + Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); + Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); + } + } + + public class TheGetAllForOwnedAndMemberRepositoriesMethod + { + [Fact] + public async Task ReturnsEveryPageOfIssues() + { + var firstPageUrl = new Uri("user/issues", UriKind.Relative); + var secondPageUrl = new Uri("https://example.com/page/2"); + var firstPageLinks = new Dictionary { { "next", secondPageUrl } }; + var firstPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(1), + CreateIssue(2), + CreateIssue(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 + { + CreateIssue(4), + CreateIssue(5), + CreateIssue(6) + }, + ApiInfo = CreateApiInfo(secondPageLinks) + }; + var lastPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(7) + }, + ApiInfo = CreateApiInfo(new Dictionary()) + }; + var gitHubClient = Substitute.For(); + gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), + Arg.Is>(d => d.Count == 4 + && d["direction"] == "desc" + && d["state"] == "open" + && d["sort"] == "created" + && d["filter"] == "assigned"), Arg.Any()) + .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 ObservableIssuesClient(gitHubClient); + + var results = await client.GetAllForOwnedAndMemberRepositories().ToArray(); + + Assert.Equal(7, results.Length); + Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); + Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); + Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); + } + } + + public class TheGetAllForOrganizationMethod + { + [Fact] + public async Task ReturnsEveryPageOfIssues() + { + var firstPageUrl = new Uri("orgs/test/issues", UriKind.Relative); + var secondPageUrl = new Uri("https://example.com/page/2"); + var firstPageLinks = new Dictionary { { "next", secondPageUrl } }; + var firstPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(1), + CreateIssue(2), + CreateIssue(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 + { + CreateIssue(4), + CreateIssue(5), + CreateIssue(6) + }, + ApiInfo = CreateApiInfo(secondPageLinks) + }; + var lastPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(7) + }, + ApiInfo = CreateApiInfo(new Dictionary()) + }; + var gitHubClient = Substitute.For(); + gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), + Arg.Is>(d => d.Count == 4 + && d["direction"] == "desc" + && d["state"] == "open" + && d["sort"] == "created" + && d["filter"] == "assigned"), Arg.Any()) + .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 ObservableIssuesClient(gitHubClient); + + var results = await client.GetAllForOrganization("test").ToArray(); + + Assert.Equal(7, results.Length); + Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); + Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); + Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); + } + } + + public class TheGetAllForCurrentMethod + { + [Fact] + public async Task ReturnsEveryPageOfIssues() + { + var firstPageUrl = new Uri("issues", UriKind.Relative); + var secondPageUrl = new Uri("https://example.com/page/2"); + var firstPageLinks = new Dictionary { { "next", secondPageUrl } }; + var firstPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(1), + CreateIssue(2), + CreateIssue(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 + { + CreateIssue(4), + CreateIssue(5), + CreateIssue(6) + }, + ApiInfo = CreateApiInfo(secondPageLinks) + }; + var lastPageResponse = new ApiResponse> + { + BodyAsObject = new List + { + CreateIssue(7) + }, + ApiInfo = CreateApiInfo(new Dictionary()) + }; + var gitHubClient = Substitute.For(); + gitHubClient.Connection.Get>(Arg.Is(firstPageUrl), + Arg.Is>(d => d.Count == 4 + && d["direction"] == "desc" + && d["state"] == "open" + && d["sort"] == "created" + && d["filter"] == "assigned"), Arg.Any()) + .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 ObservableIssuesClient(gitHubClient); + + var results = await client.GetAllForCurrent().ToArray(); + + Assert.Equal(7, results.Length); + Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number); + Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number); + Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number); + } + } + + public class TheCreateMethod + { + [Fact] + public void CreatesFromClientIssueIssue() + { + var newIssue = new NewIssue("some title"); + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + client.Create("fake", "repo", newIssue); + + gitHubClient.Issue.Received().Create("fake", "repo", newIssue); + } + + [Fact] + public void EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + Assert.Throws(() => client.Create(null, "name", new NewIssue("title"))); + Assert.Throws(() => client.Create("", "name", new NewIssue("x"))); + Assert.Throws(() => client.Create("owner", null, new NewIssue("x"))); + Assert.Throws(() => client.Create("owner", "", new NewIssue("x"))); + Assert.Throws(() => client.Create("owner", "name", null)); + } + } + + public class TheUpdateMethod + { + [Fact] + public void UpdatesClientIssueIssue() + { + var issueUpdate = new IssueUpdate(); + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + client.Update("fake", "repo", 42, issueUpdate); + + gitHubClient.Issue.Received().Update("fake", "repo", 42, issueUpdate); + } + + [Fact] + public void EnsuresArgumentsNotNull() + { + var gitHubClient = Substitute.For(); + var client = new ObservableIssuesClient(gitHubClient); + + Assert.Throws(() => client.Create(null, "name", new NewIssue("title"))); + Assert.Throws(() => client.Create("", "name", new NewIssue("x"))); + Assert.Throws(() => client.Create("owner", null, new NewIssue("x"))); + Assert.Throws(() => client.Create("owner", "", new NewIssue("x"))); + Assert.Throws(() => client.Create("owner", "name", null)); + } + } + + public class TheCtor + { + [Fact] + public void EnsuresArgument() + { + Assert.Throws(() => new IssuesClient(null)); + } + } + + static ApiInfo CreateApiInfo(IDictionary links) + { + return new ApiInfo(links, new List(), new List(), "etag", new RateLimit(new Dictionary())); + } + + static Issue CreateIssue(int issueNumber) + { + var serializer = new SimpleJsonSerializer(); + return serializer.Deserialize(@"{""number"": """ + issueNumber + @"""}"); + } +} diff --git a/Octokit/Models/Request/IssueUpdate.cs b/Octokit/Models/Request/IssueUpdate.cs index 3cc2175f..cbeedb9a 100644 --- a/Octokit/Models/Request/IssueUpdate.cs +++ b/Octokit/Models/Request/IssueUpdate.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using Octokit.Internal; @@ -10,6 +9,11 @@ namespace Octokit [DebuggerDisplay("{DebuggerDisplay,nq}")] public class IssueUpdate { + public IssueUpdate() + { + Labels = new List(); + } + /// /// Title of the milestone (required) /// @@ -45,8 +49,7 @@ namespace Octokit /// /// Only users with push access can set labels for new issues. Labels are silently dropped otherwise. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - public ICollection Labels { get; set; } + public ICollection Labels { get; private set; } /// /// Whether the issue is open or closed. @@ -63,12 +66,6 @@ namespace Octokit public void AddLabel(string name) { - // lazily create the label array - if (Labels == null) - { - Labels = new List(); - } - Labels.Add(name); } } diff --git a/Octokit/Models/Response/Issue.cs b/Octokit/Models/Response/Issue.cs index f1c065af..e7cadc96 100644 --- a/Octokit/Models/Response/Issue.cs +++ b/Octokit/Models/Response/Issue.cs @@ -13,71 +13,71 @@ namespace Octokit /// /// The URL for this milestone. /// - public Uri Url { get; set; } - public Uri HtmlUrl { get; set; } + public Uri Url { get; protected set; } + + public Uri HtmlUrl { get; protected set; } /// /// The issue number. /// - public int Number { get; set; } + public int Number { get; protected set; } /// /// Whether the issue is open or closed. /// - public ItemState State { get; set; } + public ItemState State { get; protected set; } /// /// Title of the issue /// - public string Title { get; set; } + public string Title { get; protected set; } /// /// Details about the issue. /// - public string Body { get; set; } + public string Body { get; protected set; } /// /// The user that created the issue. /// - public User User { get; set; } + public User User { get; protected set; } /// /// The set of labels applied to the issue /// - [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - public ICollection