From 270356b5b47c6eeb70ade5d12747974a6e78e5aa Mon Sep 17 00:00:00 2001 From: Jozef Izso Date: Fri, 19 Jan 2018 10:43:46 +0100 Subject: [PATCH] Fixes #1586 - Repository license API (#1630) * Implement GetLicenseContents() method for getting repository's license info * Request License Preview API for calls that return Repository object. * Add missing accept headers to observable methods for ObservableRepositoriesClients * fix impacted unit tests --- .../Clients/IObservableRepositoriesClient.cs | 21 ++++++ .../Clients/ObservableRepositoriesClient.cs | 46 ++++++++++--- .../Clients/RepositoriesClientTests.cs | 69 +++++++++++++++++++ .../Clients/RepositoriesClientTests.cs | 61 +++++++++++++--- Octokit.Tests/Helpers/Arg.cs | 5 ++ Octokit.Tests/Models/LicenseMetadataTests.cs | 32 +++++++++ .../Models/RepositoryContentLicenseTests.cs | 51 ++++++++++++++ .../ObservableRepositoriesClientTests.cs | 40 +++++------ Octokit/Clients/IRepositoriesClient.cs | 21 ++++++ Octokit/Clients/RepositoriesClient.cs | 51 +++++++++++--- Octokit/Helpers/AcceptHeaders.cs | 19 +++++ Octokit/Helpers/ApiUrls.cs | 21 ++++++ Octokit/Models/Response/License.cs | 9 +-- Octokit/Models/Response/LicenseMetadata.cs | 15 +++- Octokit/Models/Response/Repository.cs | 5 +- .../Response/RepositoryContentLicense.cs | 34 +++++++++ 16 files changed, 443 insertions(+), 57 deletions(-) create mode 100644 Octokit.Tests/Models/LicenseMetadataTests.cs create mode 100644 Octokit.Tests/Models/RepositoryContentLicenseTests.cs create mode 100644 Octokit/Models/Response/RepositoryContentLicense.cs diff --git a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs index dae1ec26..bbe9ca16 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs @@ -422,6 +422,27 @@ namespace Octokit.Reactive /// All of the repositories tags. IObservable GetAllTags(long repositoryId, ApiOptions options); + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Returns the contents of the repository's license file, if one is detected. + IObservable GetLicenseContents(string owner, string name); + + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// Returns the contents of the repository's license file, if one is detected. + IObservable GetLicenseContents(long repositoryId); + /// /// Updates the specified repository with the values given in /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs index aea66d3b..137dd466 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs @@ -129,7 +129,7 @@ namespace Octokit.Reactive /// A of . public IObservable GetAllPublic() { - return _connection.GetAndFlattenAllPages(ApiUrls.AllPublicRepositories()); + return _connection.GetAndFlattenAllPages(ApiUrls.AllPublicRepositories(), null, AcceptHeaders.LicensesApiPreview); } /// @@ -146,7 +146,7 @@ namespace Octokit.Reactive var url = ApiUrls.AllPublicRepositories(request.Since); - return _connection.GetAndFlattenAllPages(url); + return _connection.GetAndFlattenAllPages(url, null, AcceptHeaders.LicensesApiPreview); } /// @@ -172,7 +172,7 @@ namespace Octokit.Reactive { Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.Repositories(), options); + return _connection.GetAndFlattenAllPages(ApiUrls.Repositories(), null, AcceptHeaders.LicensesApiPreview, options); } /// @@ -203,7 +203,7 @@ namespace Octokit.Reactive Ensure.ArgumentNotNull(request, "request"); Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.Repositories(), request.ToParametersDictionary()); + return _connection.GetAndFlattenAllPages(ApiUrls.Repositories(), request.ToParametersDictionary(), AcceptHeaders.LicensesApiPreview); } /// @@ -229,7 +229,7 @@ namespace Octokit.Reactive Ensure.ArgumentNotNullOrEmptyString(login, "login"); Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.Repositories(login), options); + return _connection.GetAndFlattenAllPages(ApiUrls.Repositories(login), null, AcceptHeaders.LicensesApiPreview, options); } /// @@ -257,7 +257,7 @@ namespace Octokit.Reactive Ensure.ArgumentNotNullOrEmptyString(organization, "organization"); Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.OrganizationRepositories(organization), options); + return _connection.GetAndFlattenAllPages(ApiUrls.OrganizationRepositories(organization), null, AcceptHeaders.LicensesApiPreview, options); } /// @@ -265,7 +265,7 @@ namespace Octokit.Reactive /// /// /// See the Commit Status API documentation for more - /// details. Also check out the blog post + /// details. Also check out the blog post /// that announced this feature. /// public IObservableCommitStatusClient Status { get; private set; } @@ -303,7 +303,7 @@ namespace Octokit.Reactive /// /// A client for GitHub's Repository Forks API. /// - /// See Forks API documentation for more information. + /// See Forks API documentation for more information. public IObservableRepositoryForksClient Forks { get; private set; } /// @@ -649,6 +649,36 @@ namespace Octokit.Reactive return _client.Edit(owner, name, update).ToObservable(); } + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Returns the contents of the repository's license file, if one is detected. + public IObservable GetLicenseContents(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + + return _client.GetLicenseContents(owner, name).ToObservable(); + } + + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// Returns the contents of the repository's license file, if one is detected. + public IObservable GetLicenseContents(long repositoryId) + { + return _client.GetLicenseContents(repositoryId).ToObservable(); + } + /// /// Updates the specified repository with the values given in /// diff --git a/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs index 179d7022..baa6c39c 100644 --- a/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs @@ -35,6 +35,7 @@ public class RepositoriesClientTests Assert.True(repository.HasWiki); Assert.Null(repository.Homepage); Assert.NotNull(repository.DefaultBranch); + Assert.Null(repository.License); } } @@ -189,6 +190,35 @@ public class RepositoriesClientTests } } + [IntegrationTest] + public async Task CreatesARepositoryWithALicenseTemplate() + { + var github = Helper.GetAuthenticatedClient(); + var repoName = Helper.MakeNameWithTimestamp("repo-with-license"); + + var newRepository = new NewRepository(repoName) + { + AutoInit = true, + LicenseTemplate = "mit" + }; + + using (var context = await github.CreateRepositoryContext(newRepository)) + { + var createdRepository = context.Repository; + + // NOTE: the License attribute is empty for newly created repositories + Assert.Null(createdRepository.License); + + // license information is not immediatelly available after the repository is created + await Task.Delay(TimeSpan.FromSeconds(1)); + + // check for actual license by reloading repository info + var repository = await github.Repository.Get(Helper.UserName, repoName); + Assert.NotNull(repository.License); + Assert.Equal("mit", repository.License.Key); + } + } + [IntegrationTest] public async Task ThrowsInvalidGitIgnoreExceptionForInvalidTemplateNames() @@ -746,6 +776,18 @@ public class RepositoriesClientTests Assert.NotNull(repository.AllowMergeCommit); } } + + [IntegrationTest] + public async Task ReturnsSpecifiedRepositoryWithLicenseInformation() + { + var github = Helper.GetAuthenticatedClient(); + + var repository = await github.Repository.Get("github", "choosealicense.com"); + + Assert.NotNull(repository.License); + Assert.Equal("mit", repository.License.Key); + Assert.Equal("MIT License", repository.License.Name); + } } public class TheGetAllPublicMethod @@ -1612,4 +1654,31 @@ public class RepositoriesClientTests Assert.NotEqual(firstPage[4].Name, secondPage[4].Name); } } + + public class TheGetLicenseContentsMethod + { + [IntegrationTest] + public async Task ReturnsLicenseContent() + { + var github = Helper.GetAuthenticatedClient(); + + var license = await github.Repository.GetLicenseContents("octokit", "octokit.net"); + Assert.Equal("LICENSE.txt", license.Name); + Assert.NotNull(license.License); + Assert.Equal("mit", license.License.Key); + Assert.Equal("MIT License", license.License.Name); + } + + [IntegrationTest] + public async Task ReturnsLicenseContentWithRepositoryId() + { + var github = Helper.GetAuthenticatedClient(); + + var license = await github.Repository.GetLicenseContents(7528679); + Assert.Equal("LICENSE.txt", license.Name); + Assert.NotNull(license.License); + Assert.Equal("mit", license.License.Key); + Assert.Equal("MIT License", license.License.Name); + } + } } diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs index 2aea61a8..20a1de32 100644 --- a/Octokit.Tests/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs @@ -261,7 +261,7 @@ namespace Octokit.Tests.Clients connection.Received().Get( Arg.Is(u => u.ToString() == "repos/owner/name"), null, - "application/vnd.github.polaris-preview+json"); + "application/vnd.github.polaris-preview+json,application/vnd.github.drax-preview+json"); } [Fact] @@ -275,7 +275,7 @@ namespace Octokit.Tests.Clients connection.Received().Get( Arg.Is(u => u.ToString() == "repositories/1"), null, - "application/vnd.github.polaris-preview+json"); + "application/vnd.github.polaris-preview+json,application/vnd.github.drax-preview+json"); } [Fact] @@ -302,7 +302,7 @@ namespace Octokit.Tests.Clients await client.GetAllPublic(); connection.Received() - .GetAll(Arg.Is(u => u.ToString() == "repositories")); + .GetAll(Arg.Is(u => u.ToString() == "repositories"), null, "application/vnd.github.drax-preview+json"); } } @@ -317,7 +317,7 @@ namespace Octokit.Tests.Clients await client.GetAllPublic(new PublicRepositoryRequest(364L)); connection.Received() - .GetAll(Arg.Is(u => u.ToString() == "repositories?since=364")); + .GetAll(Arg.Is(u => u.ToString() == "repositories?since=364"), null, "application/vnd.github.drax-preview+json"); } [Fact] @@ -329,7 +329,7 @@ namespace Octokit.Tests.Clients await client.GetAllPublic(new PublicRepositoryRequest(364L)); connection.Received() - .GetAll(Arg.Is(u => u.ToString() == "repositories?since=364")); + .GetAll(Arg.Is(u => u.ToString() == "repositories?since=364"), null, "application/vnd.github.drax-preview+json"); } } @@ -344,7 +344,7 @@ namespace Octokit.Tests.Clients await client.GetAllForCurrent(); connection.Received() - .GetAll(Arg.Is(u => u.ToString() == "user/repos"), Args.ApiOptions); + .GetAll(Arg.Is(u => u.ToString() == "user/repos"), null, "application/vnd.github.drax-preview+json", Args.ApiOptions); } [Fact] @@ -364,6 +364,7 @@ namespace Octokit.Tests.Clients .GetAll( Arg.Is(u => u.ToString() == "user/repos"), Arg.Is>(d => d["type"] == "all"), + "application/vnd.github.drax-preview+json", Args.ApiOptions); } @@ -386,6 +387,7 @@ namespace Octokit.Tests.Clients Arg.Is(u => u.ToString() == "user/repos"), Arg.Is>(d => d["type"] == "private" && d["sort"] == "full_name"), + "application/vnd.github.drax-preview+json", Args.ApiOptions); } @@ -409,6 +411,7 @@ namespace Octokit.Tests.Clients Arg.Is(u => u.ToString() == "user/repos"), Arg.Is>(d => d["type"] == "member" && d["sort"] == "updated" && d["direction"] == "asc"), + "application/vnd.github.drax-preview+json", Args.ApiOptions); } @@ -430,6 +433,7 @@ namespace Octokit.Tests.Clients Arg.Is(u => u.ToString() == "user/repos"), Arg.Is>(d => d["visibility"] == "private"), + "application/vnd.github.drax-preview+json", Args.ApiOptions); } @@ -452,6 +456,7 @@ namespace Octokit.Tests.Clients Arg.Is(u => u.ToString() == "user/repos"), Arg.Is>(d => d["affiliation"] == "owner" && d["sort"] == "full_name"), + "application/vnd.github.drax-preview+json", Args.ApiOptions); } } @@ -467,7 +472,7 @@ namespace Octokit.Tests.Clients await client.GetAllForUser("username"); connection.Received() - .GetAll(Arg.Is(u => u.ToString() == "users/username/repos"), Args.ApiOptions); + .GetAll(Arg.Is(u => u.ToString() == "users/username/repos"), null, "application/vnd.github.drax-preview+json", Args.ApiOptions); } [Fact] @@ -496,7 +501,7 @@ namespace Octokit.Tests.Clients await client.GetAllForOrg("orgname"); connection.Received() - .GetAll(Arg.Is(u => u.ToString() == "orgs/orgname/repos"), Args.ApiOptions); + .GetAll(Arg.Is(u => u.ToString() == "orgs/orgname/repos"), null, "application/vnd.github.drax-preview+json", Args.ApiOptions); } [Fact] @@ -796,6 +801,42 @@ namespace Octokit.Tests.Clients } } + public class TheGetLicenseContentsMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + + await client.GetLicenseContents("owner", "name"); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "repos/owner/name/license"), null, "application/vnd.github.drax-preview+json"); + } + + [Fact] + public async Task RequestsTheCorrectUrlWithRepositoryId() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + + await client.GetLicenseContents(1); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "repositories/1/license"), null, "application/vnd.github.drax-preview+json"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoriesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetLicenseContents(null, "repo")); + await Assert.ThrowsAsync(() => client.GetLicenseContents("owner", null)); + } + } + public class TheGetAllTagsMethod { [Fact] @@ -892,7 +933,7 @@ namespace Octokit.Tests.Clients client.Edit("owner", "repo", update); connection.Received() - .Patch(Arg.Is(u => u.ToString() == "repos/owner/repo"), Arg.Any(), "application/vnd.github.polaris-preview+json"); + .Patch(Arg.Is(u => u.ToString() == "repos/owner/repo"), Arg.Any(), "application/vnd.github.polaris-preview+json,application/vnd.github.drax-preview+json"); } [Fact] @@ -905,7 +946,7 @@ namespace Octokit.Tests.Clients client.Edit(1, update); connection.Received() - .Patch(Arg.Is(u => u.ToString() == "repositories/1"), Arg.Any(), "application/vnd.github.polaris-preview+json"); + .Patch(Arg.Is(u => u.ToString() == "repositories/1"), Arg.Any(), "application/vnd.github.polaris-preview+json,application/vnd.github.drax-preview+json"); } [Fact] diff --git a/Octokit.Tests/Helpers/Arg.cs b/Octokit.Tests/Helpers/Arg.cs index 5408f72d..aedaa24e 100644 --- a/Octokit.Tests/Helpers/Arg.cs +++ b/Octokit.Tests/Helpers/Arg.cs @@ -72,5 +72,10 @@ namespace Octokit.Tests { get { return Arg.Any(); } } + + public static string AnyAcceptHeaders + { + get { return Arg.Any(); } + } } } diff --git a/Octokit.Tests/Models/LicenseMetadataTests.cs b/Octokit.Tests/Models/LicenseMetadataTests.cs new file mode 100644 index 00000000..9cf8d212 --- /dev/null +++ b/Octokit.Tests/Models/LicenseMetadataTests.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class LicenseMetadataTests + { + [Fact] + public void CanBeDeserializedFromLicenseJson() + { + const string json = @"{ + ""key"": ""mit"", + ""name"": ""MIT License"", + ""spdx_id"": ""MIT"", + ""url"": ""https://api.github.com/licenses/mit"", + ""featured"": true +}"; + var serializer = new SimpleJsonSerializer(); + + var license = serializer.Deserialize(json); + + Assert.Equal("mit", license.Key); + Assert.Equal("MIT License", license.Name); + Assert.Equal("MIT", license.SpdxId); + Assert.Equal("https://api.github.com/licenses/mit", license.Url); + Assert.True(license.Featured); + } + } +} + diff --git a/Octokit.Tests/Models/RepositoryContentLicenseTests.cs b/Octokit.Tests/Models/RepositoryContentLicenseTests.cs new file mode 100644 index 00000000..939279e7 --- /dev/null +++ b/Octokit.Tests/Models/RepositoryContentLicenseTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class RepositoryContentLicenseTests + { + [Fact] + public void CanBeDeserializedFromRepositoryContentLicenseJson() + { + const string json = @"{ + ""name"": ""LICENSE"", + ""path"": ""LICENSE"", + ""sha"": ""401c59dcc4570b954dd6d345e76199e1f4e76266"", + ""size"": 1077, + ""url"": ""https://api.github.com/repos/benbalter/gman/contents/LICENSE?ref=master"", + ""html_url"": ""https://github.com/benbalter/gman/blob/master/LICENSE"", + ""git_url"": ""https://api.github.com/repos/benbalter/gman/git/blobs/401c59dcc4570b954dd6d345e76199e1f4e76266"", + ""download_url"": ""https://raw.githubusercontent.com/benbalter/gman/master/LICENSE?lab=true"", + ""type"": ""file"", + ""content"": ""VGhlIE1JVCBMaWNlbnNlIChNSVQpCgpDb3B5cmlnaHQgKGMpIDIwMTMgQmVu\nIEJhbHRlcgoKUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBv\nZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEgY29weSBvZgp0\naGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmls\nZXMgKHRoZSAiU29mdHdhcmUiKSwgdG8gZGVhbCBpbgp0aGUgU29mdHdhcmUg\nd2l0aG91dCByZXN0cmljdGlvbiwgaW5jbHVkaW5nIHdpdGhvdXQgbGltaXRh\ndGlvbiB0aGUgcmlnaHRzIHRvCnVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwg\ncHVibGlzaCwgZGlzdHJpYnV0ZSwgc3VibGljZW5zZSwgYW5kL29yIHNlbGwg\nY29waWVzIG9mCnRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdCBwZXJzb25z\nIHRvIHdob20gdGhlIFNvZnR3YXJlIGlzIGZ1cm5pc2hlZCB0byBkbyBzbywK\nc3ViamVjdCB0byB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnM6CgpUaGUgYWJv\ndmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGlj\nZSBzaGFsbCBiZSBpbmNsdWRlZCBpbiBhbGwKY29waWVzIG9yIHN1YnN0YW50\naWFsIHBvcnRpb25zIG9mIHRoZSBTb2Z0d2FyZS4KClRIRSBTT0ZUV0FSRSBJ\nUyBQUk9WSURFRCAiQVMgSVMiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBL\nSU5ELCBFWFBSRVNTIE9SCklNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJ\nTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLCBG\nSVRORVNTCkZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBTkQgTk9OSU5GUklO\nR0VNRU5ULiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SUyBPUgpDT1BZ\nUklHSFQgSE9MREVSUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdF\nUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIKSU4gQU4gQUNUSU9OIE9G\nIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLCBP\nVVQgT0YgT1IgSU4KQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBU\nSEUgVVNFIE9SIE9USEVSIERFQUxJTkdTIElOIFRIRSBTT0ZUV0FSRS4K\n"", + ""encoding"": ""base64"", + ""_links"": { + ""self"": ""https://api.github.com/repos/benbalter/gman/contents/LICENSE?ref=master"", + ""git"": ""https://api.github.com/repos/benbalter/gman/git/blobs/401c59dcc4570b954dd6d345e76199e1f4e76266"", + ""html"": ""https://github.com/benbalter/gman/blob/master/LICENSE"" + }, + ""license"": { + ""key"": ""mit"", + ""name"": ""MIT License"", + ""spdx_id"": ""MIT"", + ""url"": ""https://api.github.com/licenses/mit"", + ""featured"": true + } +}"; + var serializer = new SimpleJsonSerializer(); + + var license = serializer.Deserialize(json); + var licenseMetadata = license.License; + + Assert.Equal("LICENSE", license.Name); + Assert.Equal("LICENSE", license.Path); + Assert.Equal("401c59dcc4570b954dd6d345e76199e1f4e76266", license.Sha); + Assert.NotNull(license.License); + Assert.Equal("mit", licenseMetadata.Key); + } + } +} + diff --git a/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs index b76429fa..29f6b04d 100644 --- a/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs @@ -91,18 +91,18 @@ namespace Octokit.Tests.Reactive var response = Task.Factory.StartNew>(() => new ApiResponse(new Response(), repository)); var connection = Substitute.For(); - connection.Get(Args.Uri, null, "application/vnd.github.polaris-preview+json").Returns(response); + connection.Get(Args.Uri, null, Args.AnyAcceptHeaders).Returns(response); var gitHubClient = new GitHubClient(connection); var client = new ObservableRepositoriesClient(gitHubClient); var observable = client.Get("stark", "ned"); - connection.Received(1).Get(Args.Uri, null, "application/vnd.github.polaris-preview+json"); + connection.Received(1).Get(Args.Uri, null, Args.AnyAcceptHeaders); var result = await observable; - connection.Received(1).Get(Args.Uri, null, "application/vnd.github.polaris-preview+json"); + connection.Received(1).Get(Args.Uri, null, Args.AnyAcceptHeaders); var result2 = await observable; // TODO: If we change this to a warm observable, we'll need to change this to Received(2) - connection.Received(1).Get(Args.Uri, null, "application/vnd.github.polaris-preview+json"); + connection.Received(1).Get(Args.Uri, null, Args.AnyAcceptHeaders); Assert.Same(repository, result); Assert.Same(repository, result2); @@ -117,18 +117,18 @@ namespace Octokit.Tests.Reactive var response = Task.Factory.StartNew>(() => new ApiResponse(new Response(), repository)); var connection = Substitute.For(); - connection.Get(Args.Uri, null, "application/vnd.github.polaris-preview+json").Returns(response); + connection.Get(Args.Uri, null, Args.AnyAcceptHeaders).Returns(response); var gitHubClient = new GitHubClient(connection); var client = new ObservableRepositoriesClient(gitHubClient); var observable = client.Get(1); - connection.Received(1).Get(Args.Uri, null, "application/vnd.github.polaris-preview+json"); + connection.Received(1).Get(Args.Uri, null, Args.AnyAcceptHeaders); var result = await observable; - connection.Received(1).Get(Args.Uri, null, "application/vnd.github.polaris-preview+json"); + connection.Received(1).Get(Args.Uri, null, Args.AnyAcceptHeaders); var result2 = await observable; // TODO: If we change this to a warm observable, we'll need to change this to Received(2) - connection.Received(1).Get(Args.Uri, null, "application/vnd.github.polaris-preview+json"); + connection.Received(1).Get(Args.Uri, null, Args.AnyAcceptHeaders); Assert.Same(repository, result); Assert.Same(repository, result2); @@ -182,20 +182,20 @@ namespace Octokit.Tests.Reactive }); var gitHubClient = Substitute.For(); - gitHubClient.Connection.Get>(firstPageUrl, Arg.Any>(), null) + gitHubClient.Connection.Get>(firstPageUrl, Arg.Any>(), Args.AnyAcceptHeaders) .Returns(Task.Factory.StartNew>>(() => firstPageResponse)); - gitHubClient.Connection.Get>(secondPageUrl, Arg.Any>(), null) + gitHubClient.Connection.Get>(secondPageUrl, Arg.Any>(), Args.AnyAcceptHeaders) .Returns(Task.Factory.StartNew>>(() => secondPageResponse)); - gitHubClient.Connection.Get>(thirdPageUrl, Arg.Any>(), null) + gitHubClient.Connection.Get>(thirdPageUrl, Arg.Any>(), Args.AnyAcceptHeaders) .Returns(Task.Factory.StartNew>>(() => lastPageResponse)); var repositoriesClient = new ObservableRepositoriesClient(gitHubClient); var results = await repositoriesClient.GetAllForCurrent().ToArray(); Assert.Equal(7, results.Length); - gitHubClient.Connection.Received(1).Get>(firstPageUrl, Arg.Any>(), null); - gitHubClient.Connection.Received(1).Get>(secondPageUrl, Arg.Any>(), null); - gitHubClient.Connection.Received(1).Get>(thirdPageUrl, Arg.Any>(), null); + gitHubClient.Connection.Received(1).Get>(firstPageUrl, Arg.Any>(), "application/vnd.github.drax-preview+json"); + gitHubClient.Connection.Received(1).Get>(secondPageUrl, Arg.Any>(), "application/vnd.github.drax-preview+json"); + gitHubClient.Connection.Received(1).Get>(thirdPageUrl, Arg.Any>(), "application/vnd.github.drax-preview+json"); } [Fact(Skip = "See https://github.com/octokit/octokit.net/issues/1011 for issue to investigate this further")] @@ -302,11 +302,11 @@ namespace Octokit.Tests.Reactive }); var gitHubClient = Substitute.For(); - gitHubClient.Connection.Get>(firstPageUrl, null, null) + gitHubClient.Connection.Get>(firstPageUrl, null, Args.AnyAcceptHeaders) .Returns(Task.FromResult(firstPageResponse)); - gitHubClient.Connection.Get>(secondPageUrl, null, null) + gitHubClient.Connection.Get>(secondPageUrl, null, Args.AnyAcceptHeaders) .Returns(Task.FromResult(secondPageResponse)); - gitHubClient.Connection.Get>(thirdPageUrl, null, null) + gitHubClient.Connection.Get>(thirdPageUrl, null, Args.AnyAcceptHeaders) .Returns(Task.FromResult(lastPageResponse)); var repositoriesClient = new ObservableRepositoriesClient(gitHubClient); @@ -314,9 +314,9 @@ namespace Octokit.Tests.Reactive var results = await repositoriesClient.GetAllPublic(new PublicRepositoryRequest(364L)).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); + gitHubClient.Connection.Received(1).Get>(firstPageUrl, null, "application/vnd.github.drax-preview+json"); + gitHubClient.Connection.Received(1).Get>(secondPageUrl, null, "application/vnd.github.drax-preview+json"); + gitHubClient.Connection.Received(1).Get>(thirdPageUrl, null, "application/vnd.github.drax-preview+json"); } } diff --git a/Octokit/Clients/IRepositoriesClient.cs b/Octokit/Clients/IRepositoriesClient.cs index bab7c37e..f77f0bfb 100644 --- a/Octokit/Clients/IRepositoriesClient.cs +++ b/Octokit/Clients/IRepositoriesClient.cs @@ -527,6 +527,27 @@ namespace Octokit /// All of the repositories tags. Task> GetAllTags(long repositoryId, ApiOptions options); + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Returns the contents of the repository's license file, if one is detected. + Task GetLicenseContents(string owner, string name); + + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// Returns the contents of the repository's license file, if one is detected. + Task GetLicenseContents(long repositoryId); + /// /// Updates the specified repository with the values given in /// diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index e7a9b044..70070c62 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -177,7 +177,7 @@ namespace Octokit Ensure.ArgumentNotNull(update, "update"); Ensure.ArgumentNotNull(update.Name, "update.Name"); - return ApiConnection.Patch(ApiUrls.Repository(owner, name), update, AcceptHeaders.SquashCommitPreview); + return ApiConnection.Patch(ApiUrls.Repository(owner, name), update, AcceptHeaders.Concat(AcceptHeaders.SquashCommitPreview, AcceptHeaders.LicensesApiPreview)); } /// @@ -190,7 +190,7 @@ namespace Octokit { Ensure.ArgumentNotNull(update, "update"); - return ApiConnection.Patch(ApiUrls.Repository(repositoryId), update, AcceptHeaders.SquashCommitPreview); + return ApiConnection.Patch(ApiUrls.Repository(repositoryId), update, AcceptHeaders.Concat(AcceptHeaders.SquashCommitPreview, AcceptHeaders.LicensesApiPreview)); } /// @@ -208,7 +208,7 @@ namespace Octokit Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); - return ApiConnection.Get(ApiUrls.Repository(owner, name), null, AcceptHeaders.SquashCommitPreview); + return ApiConnection.Get(ApiUrls.Repository(owner, name), null, AcceptHeaders.Concat(AcceptHeaders.SquashCommitPreview, AcceptHeaders.LicensesApiPreview)); } /// @@ -222,7 +222,7 @@ namespace Octokit /// A public Task Get(long repositoryId) { - return ApiConnection.Get(ApiUrls.Repository(repositoryId), null, AcceptHeaders.SquashCommitPreview); + return ApiConnection.Get(ApiUrls.Repository(repositoryId), null, AcceptHeaders.Concat(AcceptHeaders.SquashCommitPreview, AcceptHeaders.LicensesApiPreview)); } /// @@ -237,7 +237,7 @@ namespace Octokit /// A of . public Task> GetAllPublic() { - return ApiConnection.GetAll(ApiUrls.AllPublicRepositories()); + return ApiConnection.GetAll(ApiUrls.AllPublicRepositories(), null, AcceptHeaders.LicensesApiPreview); } /// @@ -257,7 +257,7 @@ namespace Octokit var url = ApiUrls.AllPublicRepositories(request.Since); - return ApiConnection.GetAll(url); + return ApiConnection.GetAll(url, null, AcceptHeaders.LicensesApiPreview); } /// @@ -289,7 +289,7 @@ namespace Octokit { Ensure.ArgumentNotNull(options, "options"); - return ApiConnection.GetAll(ApiUrls.Repositories(), options); + return ApiConnection.GetAll(ApiUrls.Repositories(), null, AcceptHeaders.LicensesApiPreview, options); } /// @@ -315,7 +315,7 @@ namespace Octokit Ensure.ArgumentNotNull(request, "request"); Ensure.ArgumentNotNull(options, "options"); - return ApiConnection.GetAll(ApiUrls.Repositories(), request.ToParametersDictionary(), options); + return ApiConnection.GetAll(ApiUrls.Repositories(), request.ToParametersDictionary(), AcceptHeaders.LicensesApiPreview, options); } /// @@ -350,7 +350,7 @@ namespace Octokit Ensure.ArgumentNotNullOrEmptyString(login, "login"); Ensure.ArgumentNotNull(options, "options"); - return ApiConnection.GetAll(ApiUrls.Repositories(login), options); + return ApiConnection.GetAll(ApiUrls.Repositories(login), null, AcceptHeaders.LicensesApiPreview, options); } /// @@ -384,7 +384,7 @@ namespace Octokit Ensure.ArgumentNotNullOrEmptyString(organization, "organization"); Ensure.ArgumentNotNull(options, "options"); - return ApiConnection.GetAll(ApiUrls.OrganizationRepositories(organization), options); + return ApiConnection.GetAll(ApiUrls.OrganizationRepositories(organization), null, AcceptHeaders.LicensesApiPreview, options); } /// @@ -808,6 +808,37 @@ namespace Octokit return ApiConnection.GetAll(ApiUrls.RepositoryTags(repositoryId), options); } + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Returns the contents of the repository's license file, if one is detected. + public Task GetLicenseContents(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + + return ApiConnection.Get(ApiUrls.RepositoryLicense(owner, name), null, AcceptHeaders.LicensesApiPreview); + + } + + /// + /// Get the contents of a repository's license + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// Returns the contents of the repository's license file, if one is detected. + public Task GetLicenseContents(long repositoryId) + { + return ApiConnection.Get(ApiUrls.RepositoryLicense(repositoryId), null, AcceptHeaders.LicensesApiPreview); + } + /// /// A client for GitHub's Repository Pages API. /// diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index afd058d7..32b6a883 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -14,6 +14,10 @@ namespace Octokit public const string OrganizationPermissionsPreview = "application/vnd.github.ironman-preview+json"; + /// + /// Support for retrieving information about open source license usage on GitHub.com. + /// Custom media type: drax-preview Announced: 2015-03-09 Update 1: 2015-06-24 Update 2: 2015-08-04 + /// public const string LicensesApiPreview = "application/vnd.github.drax-preview+json"; public const string ProtectedBranchesApiPreview = "application/vnd.github.loki-preview+json"; @@ -50,5 +54,20 @@ namespace Octokit public const string OrganizationMembershipPreview = "application/vnd.github.korra-preview+json"; public const string NestedTeamsPreview = "application/vnd.github.hellcat-preview+json"; + + /// + /// Combines multiple preview headers. GitHub API supports Accept header with multiple + /// values separated by comma. + /// + /// Accept header values that will be combine to single Accept header. + /// + /// This Accept header application/vnd.github.loki-preview+json,application/vnd.github.drax-preview+json + /// indicated we want both Protected Branches and Licenses preview APIs. + /// + /// Accept header value. + public static string Concat(params string[] headers) + { + return string.Join(",", headers); + } } } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index a9c18556..9e30cb83 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -3697,5 +3697,26 @@ namespace Octokit { return "projects/columns/cards/{0}/moves".FormatUri(id); } + + /// + /// Returns the for repository's license requests. + /// + /// The owner of repo + /// The name of repo + /// The for repository's license requests. + public static Uri RepositoryLicense(string owner, string repo) + { + return "repos/{0}/{1}/license".FormatUri(owner, repo); + } + + /// + /// Returns the for repository's license requests. + /// + /// The id of the repository + /// The for repository's license requests. + public static Uri RepositoryLicense(long repositoryId) + { + return "repositories/{0}/license".FormatUri(repositoryId); + } } } diff --git a/Octokit/Models/Response/License.cs b/Octokit/Models/Response/License.cs index 41c2584d..61cc60f6 100644 --- a/Octokit/Models/Response/License.cs +++ b/Octokit/Models/Response/License.cs @@ -12,6 +12,7 @@ namespace Octokit public License( string key, string name, + string spdxId, string url, string htmlUrl, bool featured, @@ -21,7 +22,7 @@ namespace Octokit string body, IEnumerable required, IEnumerable permitted, - IEnumerable forbidden) : base(key, name, url) + IEnumerable forbidden) : base(key, name, spdxId, url, featured) { Ensure.ArgumentNotNull(htmlUrl, "htmlUrl"); Ensure.ArgumentNotNull(description, "description"); @@ -33,7 +34,6 @@ namespace Octokit Ensure.ArgumentNotNull(forbidden, "forbidden"); HtmlUrl = htmlUrl; - Featured = featured; Description = description; Category = category; Implementation = implementation; @@ -52,11 +52,6 @@ namespace Octokit /// public string HtmlUrl { get; protected set; } - /// - /// Whether the license is one of the licenses featured on https://choosealicense.com - /// - public bool Featured { get; protected set; } - /// /// A description of the license. /// diff --git a/Octokit/Models/Response/LicenseMetadata.cs b/Octokit/Models/Response/LicenseMetadata.cs index 5529570b..bba39bf2 100644 --- a/Octokit/Models/Response/LicenseMetadata.cs +++ b/Octokit/Models/Response/LicenseMetadata.cs @@ -6,15 +6,18 @@ namespace Octokit [DebuggerDisplay("{DebuggerDisplay,nq}")] public class LicenseMetadata { - public LicenseMetadata(string key, string name, string url) + public LicenseMetadata(string key, string name, string spdxId, string url, bool featured) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(spdxId, "spdxId"); Ensure.ArgumentNotNull(url, "url"); Key = key; Name = name; + SpdxId = spdxId; Url = url; + Featured = featured; } public LicenseMetadata() @@ -31,11 +34,21 @@ namespace Octokit /// public string Name { get; protected set; } + /// + /// SPDX license identifier. + /// + public string SpdxId { get; protected set; } + /// /// URL to retrieve details about a license. /// public string Url { get; protected set; } + /// + /// Whether the license is one of the licenses featured on https://choosealicense.com + /// + public bool Featured { get; protected set; } + internal virtual string DebuggerDisplay { get diff --git a/Octokit/Models/Response/Repository.cs b/Octokit/Models/Response/Repository.cs index 575d8404..77d87140 100644 --- a/Octokit/Models/Response/Repository.cs +++ b/Octokit/Models/Response/Repository.cs @@ -14,7 +14,7 @@ namespace Octokit Id = id; } - public Repository(string url, string htmlUrl, string cloneUrl, string gitUrl, string sshUrl, string svnUrl, string mirrorUrl, long id, User owner, string name, string fullName, string description, string homepage, string language, bool @private, bool fork, int forksCount, int stargazersCount, string defaultBranch, int openIssuesCount, DateTimeOffset? pushedAt, DateTimeOffset createdAt, DateTimeOffset updatedAt, RepositoryPermissions permissions, Repository parent, Repository source, bool hasIssues, bool hasWiki, bool hasDownloads, bool hasPages, int subscribersCount, long size, bool? allowRebaseMerge, bool? allowSquashMerge, bool? allowMergeCommit) + public Repository(string url, string htmlUrl, string cloneUrl, string gitUrl, string sshUrl, string svnUrl, string mirrorUrl, long id, User owner, string name, string fullName, string description, string homepage, string language, bool @private, bool fork, int forksCount, int stargazersCount, string defaultBranch, int openIssuesCount, DateTimeOffset? pushedAt, DateTimeOffset createdAt, DateTimeOffset updatedAt, RepositoryPermissions permissions, Repository parent, Repository source, LicenseMetadata license, bool hasIssues, bool hasWiki, bool hasDownloads, bool hasPages, int subscribersCount, long size, bool? allowRebaseMerge, bool? allowSquashMerge, bool? allowMergeCommit) { Url = url; HtmlUrl = htmlUrl; @@ -42,6 +42,7 @@ namespace Octokit Permissions = permissions; Parent = parent; Source = source; + License = license; HasIssues = hasIssues; HasWiki = hasWiki; HasDownloads = hasDownloads; @@ -105,6 +106,8 @@ namespace Octokit public Repository Source { get; protected set; } + public LicenseMetadata License { get; protected set; } + public bool HasIssues { get; protected set; } public bool HasWiki { get; protected set; } diff --git a/Octokit/Models/Response/RepositoryContentLicense.cs b/Octokit/Models/Response/RepositoryContentLicense.cs new file mode 100644 index 00000000..01082a66 --- /dev/null +++ b/Octokit/Models/Response/RepositoryContentLicense.cs @@ -0,0 +1,34 @@ +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RepositoryContentLicense : RepositoryContentInfo + { + public RepositoryContentLicense(LicenseMetadata license, string name, string path, string sha, int size, ContentType type, string downloadUrl, string url, string gitUrl, string htmlUrl) + : base(name, path, sha, size, type, downloadUrl, url, gitUrl, htmlUrl) + { + Ensure.ArgumentNotNull(license, "license"); + + License = license; + } + + public RepositoryContentLicense() + { + } + + /// + /// License information + /// + public LicenseMetadata License { get; protected set; } + + internal new string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "License: {0} {1}", this.License?.Key, base.DebuggerDisplay); + } + } + } +}