diff --git a/Octokit.Reactive/Clients/IObservableOrganizationCustomPropertiesClient.cs b/Octokit.Reactive/Clients/IObservableOrganizationCustomPropertiesClient.cs new file mode 100644 index 0000000000..eb3a98c965 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableOrganizationCustomPropertiesClient.cs @@ -0,0 +1,72 @@ +using System; +using System.Reactive; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's Organization Custom Properties API. + /// + /// + /// See Custom Properties API documentation for more information. + /// + public interface IObservableOrganizationCustomPropertiesClient + { + /// + /// Get all custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + IObservable GetAll(string org); + + /// + /// Get a single custom property by name. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + IObservable Get(string org, string propertyName); + + /// + /// Create new or update existing custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom properties to create or update + IObservable CreateOrUpdate(string org, UpsertOrganizationCustomProperties properties); + + /// + /// Create new or update existing custom property for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + /// The custom property to create or update + IObservable CreateOrUpdate(string org, string propertyName, UpsertOrganizationCustomProperty property); + + /// + /// Removes a custom property that is defined for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + IObservable Delete(string org, string propertyName); + + /// + /// A client for GitHub's Organization Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + IObservableOrganizationCustomPropertyValuesClient Values { get; } + } +} diff --git a/Octokit.Reactive/Clients/IObservableOrganizationCustomPropertyValuesClient.cs b/Octokit.Reactive/Clients/IObservableOrganizationCustomPropertyValuesClient.cs new file mode 100644 index 0000000000..cf46c98025 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableOrganizationCustomPropertyValuesClient.cs @@ -0,0 +1,55 @@ +using System; +using System.Reactive; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's Organization Custom Property Values API. + /// + /// + /// See Custom Properties API documentation for more information. + /// + public interface IObservableOrganizationCustomPropertyValuesClient + { + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + IObservable GetAll(string org); + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Options for changing the API response + IObservable GetAll(string org, ApiOptions options); + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Finds repositories in the organization with a query containing one or more search keywords and qualifiers. + IObservable GetAll(string org, OrganizationCustomPropertyValuesRequest repositoryQuery); + + /// + /// Create new or update existing custom property values for repositories an organization. + /// Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// A maximum of 30 repositories can be updated in a single request. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom property values to create or update + IObservable CreateOrUpdate(string org, UpsertOrganizationCustomPropertyValues propertyValues); + } +} diff --git a/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs b/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs index d44a187990..673b132dfb 100644 --- a/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs +++ b/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs @@ -31,6 +31,11 @@ public interface IObservableOrganizationsClient /// IObservableOrganizationActionsClient Actions { get; } + /// + /// Returns a client to manage organization custom properties. + /// + IObservableOrganizationCustomPropertiesClient CustomProperty { get; } + /// /// Returns the specified organization. /// diff --git a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs index 763c56056f..18d14e306b 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs @@ -255,6 +255,14 @@ public interface IObservableRepositoriesClient /// IObservableRepositoryCommentsClient Comment { get; } + /// + /// Client for GitHub's Repository Custom Property Values API. + /// + /// + /// See the Repository Custom Property API documentation for more information. + /// + IObservableRepositoryCustomPropertiesClient CustomProperty { get; } + /// /// A client for GitHub's Repository Hooks API. /// diff --git a/Octokit.Reactive/Clients/IObservableRepositoryCustomPropertiesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoryCustomPropertiesClient.cs new file mode 100644 index 0000000000..b624a87077 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableRepositoryCustomPropertiesClient.cs @@ -0,0 +1,35 @@ +using System; +using System.Reactive; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's Repository Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + public interface IObservableRepositoryCustomPropertiesClient + { + /// + /// Get all custom property values for a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + IObservable GetAll(string owner, string repoName); + + /// + /// Create new or update existing custom property values for a repository. Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The custom property values to create or update + IObservable CreateOrUpdate(string owner, string repoName, UpsertRepositoryCustomPropertyValues propertyValues); + } +} diff --git a/Octokit.Reactive/Clients/ObservableOrganizationCustomPropertiesClient.cs b/Octokit.Reactive/Clients/ObservableOrganizationCustomPropertiesClient.cs new file mode 100644 index 0000000000..dadd0fa2a8 --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableOrganizationCustomPropertiesClient.cs @@ -0,0 +1,113 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; + +namespace Octokit.Reactive +{ + public class ObservableOrganizationCustomPropertiesClient : IObservableOrganizationCustomPropertiesClient + { + readonly IOrganizationCustomPropertiesClient _client; + readonly IConnection _connection; + + public ObservableOrganizationCustomPropertiesClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, nameof(client)); + + Values = new ObservableOrganizationCustomPropertyValuesClient(client); + + _client = client.Organization.CustomProperty; + _connection = client.Connection; + } + + /// + /// Get all custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + public IObservable GetAll(string org) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return _client.GetAll(org).ToObservable().SelectMany(p => p); + } + + /// + /// Get a single custom property by name. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + public IObservable Get(string org, string propertyName) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(propertyName, nameof(propertyName)); + + return _client.Get(org, propertyName).ToObservable(); + } + + /// + /// Create new or update existing custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom properties to create or update + public IObservable CreateOrUpdate(string org, UpsertOrganizationCustomProperties properties) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(properties, nameof(properties)); + Ensure.ArgumentNotNullOrEmptyEnumerable(properties.Properties, nameof(properties.Properties)); + + return _client.CreateOrUpdate(org, properties).ToObservable().SelectMany(p => p); + } + + /// + /// Create new or update existing custom property for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + /// The custom property to create or update + public IObservable CreateOrUpdate(string org, string propertyName, UpsertOrganizationCustomProperty property) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(propertyName, nameof(propertyName)); + Ensure.ArgumentNotNull(property, nameof(property)); + Ensure.ArgumentNotNullOrDefault(property.ValueType, nameof(property.ValueType)); + + return _client.CreateOrUpdate(org, propertyName, property).ToObservable(); + } + + /// + /// Removes a custom property that is defined for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + public IObservable Delete(string org, string propertyName) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(propertyName, nameof(propertyName)); + + return _client.Delete(org, propertyName).ToObservable(); + } + + /// + /// A client for GitHub's Organization Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + public IObservableOrganizationCustomPropertyValuesClient Values { get; private set; } + } +} diff --git a/Octokit.Reactive/Clients/ObservableOrganizationCustomPropertyValuesClient.cs b/Octokit.Reactive/Clients/ObservableOrganizationCustomPropertyValuesClient.cs new file mode 100644 index 0000000000..bc5c4441a8 --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableOrganizationCustomPropertyValuesClient.cs @@ -0,0 +1,95 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using Octokit.Reactive.Internal; + +namespace Octokit.Reactive +{ + public class ObservableOrganizationCustomPropertyValuesClient : IObservableOrganizationCustomPropertyValuesClient + { + readonly IOrganizationCustomPropertyValuesClient _client; + readonly IConnection _connection; + + public ObservableOrganizationCustomPropertyValuesClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, nameof(client)); + + _client = client.Organization.CustomProperty.Values; + _connection = client.Connection; + } + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Thrown when a general API error occurs. + public IObservable GetAll(string org) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return GetAll(org, new OrganizationCustomPropertyValuesRequest()); + } + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Options for changing the API response + public IObservable GetAll(string org, ApiOptions options) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(options, nameof(options)); + + var url = ApiUrls.OrganizationCustomPropertyValues(org); + + return _connection.GetAndFlattenAllPages(url, options); + } + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Finds repositories in the organization with a query containing one or more search keywords and qualifiers. + /// Thrown when a general API error occurs. + public IObservable GetAll(string org, OrganizationCustomPropertyValuesRequest repositoryQuery) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(repositoryQuery, nameof(repositoryQuery)); + + var url = ApiUrls.OrganizationCustomPropertyValues(org); + + return _connection.GetAndFlattenAllPages(url, repositoryQuery.Parameters); + } + + /// + /// Create new or update existing custom property values for repositories an organization. + /// Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// A maximum of 30 repositories can be updated in a single request. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom property values to create or update + /// Thrown when a general API error occurs. + public IObservable CreateOrUpdate(string org, UpsertOrganizationCustomPropertyValues propertyValues) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(propertyValues, nameof(propertyValues)); + Ensure.ArgumentNotNullOrEmptyEnumerable(propertyValues.Properties, nameof(propertyValues.Properties)); + Ensure.ArgumentNotNullOrEmptyEnumerable(propertyValues.RepositoryNames, nameof(propertyValues.RepositoryNames)); + + return _client.CreateOrUpdate(org, propertyValues).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs b/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs index e90bac920f..ba00ebf32d 100644 --- a/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs +++ b/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs @@ -23,6 +23,7 @@ public ObservableOrganizationsClient(IGitHubClient client) Hook = new ObservableOrganizationHooksClient(client); OutsideCollaborator = new ObservableOrganizationOutsideCollaboratorsClient(client); Actions = new ObservableOrganizationActionsClient(client); + CustomProperty = new ObservableOrganizationCustomPropertiesClient(client); _client = client.Organization; _connection = client.Connection; @@ -54,6 +55,11 @@ public ObservableOrganizationsClient(IGitHubClient client) /// public IObservableOrganizationActionsClient Actions { get; private set; } + /// + /// Returns a client to manage organization custom properties. + /// + public IObservableOrganizationCustomPropertiesClient CustomProperty { get; private set; } + /// /// Returns the specified organization. /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs index 26f35b1f6a..3cfd202b7c 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs @@ -33,6 +33,7 @@ public ObservableRepositoriesClient(IGitHubClient client) Branch = new ObservableRepositoryBranchesClient(client); Comment = new ObservableRepositoryCommentsClient(client); Commit = new ObservableRepositoryCommitsClient(client); + CustomProperty = new ObservableRepositoryCustomPropertiesClient(client); Release = new ObservableReleasesClient(client); DeployKeys = new ObservableRepositoryDeployKeysClient(client); Content = new ObservableRepositoryContentsClient(client); @@ -380,6 +381,14 @@ public IObservable GetAllForOrg(string organization, ApiOptions opti /// public IObservableRepositoryCommentsClient Comment { get; private set; } + /// + /// Client for GitHub's Repository Custom Property Values API. + /// + /// + /// See the Repository Custom Property API documentation for more information. + /// + public IObservableRepositoryCustomPropertiesClient CustomProperty { get; private set; } + /// /// A client for GitHub's Repository Hooks API. /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoryCustomPropertiesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoryCustomPropertiesClient.cs new file mode 100644 index 0000000000..068012a888 --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableRepositoryCustomPropertiesClient.cs @@ -0,0 +1,64 @@ +using System.Reactive.Threading.Tasks; +using System; +using System.Reactive; +using System.Reactive.Linq; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's Repository Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + public class ObservableRepositoryCustomPropertiesClient : IObservableRepositoryCustomPropertiesClient + { + readonly IRepositoryCustomPropertiesClient _client; + readonly IConnection _connection; + + public ObservableRepositoryCustomPropertiesClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, nameof(client)); + + _client = client.Repository.CustomProperty; + _connection = client.Connection; + } + + /// + /// Get all custom property values for a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + /// Thrown when a general API error occurs. + public IObservable GetAll(string owner, string repoName) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repoName, nameof(repoName)); + + return _client.GetAll(owner, repoName).ToObservable().SelectMany(p => p); + } + + /// + /// Create new or update existing custom property values for a repository. Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The custom property values to create or update + /// Thrown when a general API error occurs. + public IObservable CreateOrUpdate(string owner, string repoName, UpsertRepositoryCustomPropertyValues propertyValues) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repoName, nameof(repoName)); + Ensure.ArgumentNotNull(propertyValues, nameof(propertyValues)); + Ensure.ArgumentNotNullOrEmptyEnumerable(propertyValues.Properties, nameof(propertyValues.Properties)); + + return _client.CreateOrUpdate(owner, repoName, propertyValues).ToObservable(); + } + } +} diff --git a/Octokit.Tests.Integration/Clients/OrganizationCustomPropertiesClientTests.cs b/Octokit.Tests.Integration/Clients/OrganizationCustomPropertiesClientTests.cs new file mode 100644 index 0000000000..a49d538998 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/OrganizationCustomPropertiesClientTests.cs @@ -0,0 +1,114 @@ +using System.Threading.Tasks; +using Xunit; + +#if SODIUM_CORE_AVAILABLE +using Sodium; +#endif + +namespace Octokit.Tests.Integration.Clients +{ + public class OrganizationCustomPropertiesClientTests + { + public class GetAllMethod + { + [OrganizationTest] + public async Task GetCustomProperties() + { + var github = Helper.GetAuthenticatedClient(); + + var customProperties = await github.Organization.CustomProperty.GetAll(Helper.Organization); + + Assert.NotEmpty(customProperties); + } + } + + /// + /// Please create a custom property in your organization called TEST + /// + public class GetMethod + { + [OrganizationTest] + public async Task GetCustomProperty() + { + var github = Helper.GetAuthenticatedClient(); + + var customProperty = await github.Organization.CustomProperty.Get(Helper.Organization, "TEST"); + + Assert.NotNull(customProperty); + Assert.Equal("TEST", customProperty.PropertyName); + } + } + + public class CreateOrUpdateMethod + { +#if SODIUM_CORE_AVAILABLE + [OrganizationTest] + public async Task UpsertCustomProperty() + { + var github = Helper.GetAuthenticatedClient(); + var upsertValue = GetCustomPropertyUpdateForCreate("value"); + + var customProperty = await github.Organization.CustomProperty.CreateOrUpdate(Helper.Organization, "UPSERT_TEST", upsertValue); + + Assert.NotNull(customProperty); + Assert.Equal("UPSERT_TEST", customProperty.PropertyName); + } +#endif + } + + public class DeleteMethod + { +#if SODIUM_CORE_AVAILABLE + [OrganizationTest] + public async Task DeleteCustomProperty() + { + var github = Helper.GetAuthenticatedClient(); + + var propertyName = "DELETE_TEST"; + + var upsertValue = GetCustomPropertyUpdateForCreate("value"); + + await github.Organization.CustomProperty.CreateOrUpdate(Helper.Organization, propertyName, upsertValue); + await github.Organization.CustomProperty.Delete(Helper.Organization, propertyName); + } +#endif + } + + +#if SODIUM_CORE_AVAILABLE + private static UpsertOrganizationCustomProperties GetCustomPropertiesForCreate(string propertyName, string value) + { + var properties = new UpsertOrganizationCustomProperties + { + Properties = new List { GetCustomPropertyUpdateForCreate(propertyName, value) }; + }; + + return upsertValue; + } + + private static OrganizationCustomPropertyUpdate GetCustomPropertyUpdateForCreate(string propertyName, string value) + { + return new OrganizationCustomPropertyUpdate(propertyName, CustomPropertyValueType.String, value); + } + + private static UpsertOrganizationCustomProperty GetCustomPropertyUpdateForUpdate(string value) + { + return new UpsertOrganizationCustomProperty(CustomPropertyValueType.String, value); + } +#endif + + private static async Task CreateRepoIfNotExists(IGitHubClient github, string name) + { + try + { + var existingRepo = await github.Repository.Get(Helper.Organization, name); + return existingRepo; + } + catch + { + var newRepo = await github.Repository.Create(Helper.Organization, new NewRepository(name)); + return newRepo; + } + } + } +} diff --git a/Octokit.Tests.Integration/Clients/OrganizationCustomPropertyValuesClientTests.cs b/Octokit.Tests.Integration/Clients/OrganizationCustomPropertyValuesClientTests.cs new file mode 100644 index 0000000000..24169eb251 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/OrganizationCustomPropertyValuesClientTests.cs @@ -0,0 +1,59 @@ +using System.Threading.Tasks; +using Xunit; + +#if SODIUM_CORE_AVAILABLE +using Sodium; +#endif + +namespace Octokit.Tests.Integration.Clients +{ + /// + /// Access to view and update custom property values is required for the following tests + /// + public class OrganizationCustomPropertyValuesClientTests + { + /// + /// Fill these in for tests to work + /// + internal const string OWNER = "octokit"; + internal const string REPO = "octokit.net"; + + public class GetAllMethod + { + [IntegrationTest] + public async Task GetPropertyValues() + { + var github = Helper.GetAuthenticatedClient(); + + var propertyValues = await github.Organization.CustomProperty.Values.GetAll(OWNER); + + Assert.NotEmpty(propertyValues); + } + } + + public class CreateOrUpdateMethod + { +#if SODIUM_CORE_AVAILABLE + [IntegrationTest] + public async Task UpsertPropertyValues() + { + var github = Helper.GetAuthenticatedClient(); + + var upsertValue = new UpsertOrganizationCustomPropertyValues + { + RepositoryNames = new List { "repo" }, + Properties = new List + { + new CustomPropertyValueUpdate("UPSERT_TEST", "value") + } + }; + + var updatedValues = await github.Organization.CustomProperty.Values.CreateOrUpdate(OWNER, REPO, upsertValue); + + Assert.NotNull(updatedValues); + Assert.True(updatedValues.Count > 0); + } +#endif + } + } +} diff --git a/Octokit.Tests.Integration/Clients/RepositoryCustomPropertiesClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryCustomPropertiesClientTests.cs new file mode 100644 index 0000000000..9b8994c4b9 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/RepositoryCustomPropertiesClientTests.cs @@ -0,0 +1,59 @@ +using System.Threading.Tasks; +using Xunit; + +#if SODIUM_CORE_AVAILABLE +using Sodium; +#endif + +namespace Octokit.Tests.Integration.Clients +{ + /// + /// Access to view and update custom property values is required for the following tests + /// + public class RepositoryCustomPropertiesClientTests + { + /// + /// Fill these in for tests to work + /// + internal const string OWNER = "octokit"; + internal const string REPO = "octokit.net"; + + public class GetAllMethod + { + [IntegrationTest] + public async Task GetPropertyValues() + { + var github = Helper.GetAuthenticatedClient(); + + var propertyValues = await github.Repository.CustomProperty.GetAll(OWNER, REPO); + + Assert.NotEmpty(propertyValues); + } + } + + public class CreateOrUpdateMethod + { +#if SODIUM_CORE_AVAILABLE + [IntegrationTest] + public async Task UpsertPropertyValues() + { + var github = Helper.GetAuthenticatedClient(); + var now = DateTime.Now; + + var upsertValue = new UpsertRepositoryCustomPropertyValues + { + Properties = new List + { + new CustomPropertyValueUpdate("TEST", "UPSERT_TEST") + } + }; + + var updatedValues = await github.Repository.CustomProperty.CreateOrUpdate(OWNER, REPO, upsertValue); + + Assert.NotNull(updatedValues); + Assert.True(updatedValues.Count > 0); + } +#endif + } + } +} diff --git a/Octokit.Tests/Clients/OrganizationCustomPropertiesClientTests.cs b/Octokit.Tests/Clients/OrganizationCustomPropertiesClientTests.cs new file mode 100644 index 0000000000..9675054051 --- /dev/null +++ b/Octokit.Tests/Clients/OrganizationCustomPropertiesClientTests.cs @@ -0,0 +1,172 @@ +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class OrganizationCustomPropertiesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new OrganizationCustomPropertiesClient(null)); + } + } + + public class GetAllMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertiesClient(connection); + + await client.GetAll("org"); + + connection.Received() + .Get>(Arg.Is(u => u.ToString() == "orgs/org/properties/schema"), null); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null)); + await Assert.ThrowsAsync(() => client.GetAll("")); + } + } + + public class GetMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertiesClient(connection); + + await client.Get("org", "property"); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "orgs/org/properties/schema/property")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Get(null, "property")); + await Assert.ThrowsAsync(() => client.Get("org", null)); + + await Assert.ThrowsAsync(() => client.Get("", "property")); + await Assert.ThrowsAsync(() => client.Get("org", "")); + } + } + + public class BatchCreateOrUpdateMethod + { + [Fact] + public async Task PatchTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertiesClient(connection); + var properties = new UpsertOrganizationCustomProperties + { + Properties = new List + { + new OrganizationCustomPropertyUpdate("name", CustomPropertyValueType.String, "default") + } + }; + + await client.CreateOrUpdate("org", properties); + + connection.Received() + .Patch>(Arg.Is(u => u.ToString() == "orgs/org/properties/schema"), properties); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationCustomPropertiesClient(Substitute.For()); + + var properties = new UpsertOrganizationCustomProperties + { + Properties = new List + { + new OrganizationCustomPropertyUpdate("name", CustomPropertyValueType.String, "default") + } + }; + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, properties)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", null)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", new UpsertOrganizationCustomProperties())); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", properties)); + } + } + + public class CreateOrUpdateMethod + { + [Fact] + public async Task PostsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertiesClient(connection); + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.String, "value"); + + await client.CreateOrUpdate("org", "property", update); + + connection.Received() + .Put(Arg.Is(u => u.ToString() == "orgs/org/properties/schema/property"), update); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationCustomPropertiesClient(Substitute.For()); + + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.String, "value"); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, "property", update)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", null, update)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", "property", null)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", "property", new UpsertOrganizationCustomProperty())); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", "property", update)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", "", update)); + } + } + + public class DeleteMethod + { + [Fact] + public async Task DeletesTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertiesClient(connection); + + await client.Delete("org", "property"); + + connection.Received() + .Delete(Arg.Is(u => u.ToString() == "orgs/org/properties/schema/property")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Delete(null, "property")); + await Assert.ThrowsAsync(() => client.Delete("owner", null)); + + await Assert.ThrowsAsync(() => client.Delete("", "property")); + await Assert.ThrowsAsync(() => client.Delete("owner", "")); + } + } + } +} diff --git a/Octokit.Tests/Clients/OrganizationCustomPropertyValuesClientTests.cs b/Octokit.Tests/Clients/OrganizationCustomPropertyValuesClientTests.cs new file mode 100644 index 0000000000..1b363ab661 --- /dev/null +++ b/Octokit.Tests/Clients/OrganizationCustomPropertyValuesClientTests.cs @@ -0,0 +1,110 @@ +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class OrganizationCustomPropertyValuesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new OrganizationCustomPropertyValuesClient(null)); + } + } + + public class GetAllMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertyValuesClient(connection); + + await client.GetAll("org"); + + connection.Received() + .GetAll(Arg.Is(u => u.ToString() == "orgs/org/properties/values"), Arg.Any>()); + } + + [Fact] + public void RequestsTheCorrectUrlWithApiOptions() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertyValuesClient(connection); + + var options = new ApiOptions + { + PageCount = 1, + StartPage = 1, + PageSize = 1 + }; + + client.GetAll("org", options); + + connection.Received() + .GetAll(Arg.Is(u => u.ToString() == "orgs/org/properties/values"), options); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationCustomPropertyValuesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null)); + await Assert.ThrowsAsync(() => client.GetAll(null, new OrganizationCustomPropertyValuesRequest())); + await Assert.ThrowsAsync(() => client.GetAll("org", repositoryQuery: null)); + + await Assert.ThrowsAsync(() => client.GetAll("")); + await Assert.ThrowsAsync(() => client.GetAll("", new OrganizationCustomPropertyValuesRequest())); + } + } + + public class CreateOrUpdateMethod + { + [Fact] + public async Task PatchTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationCustomPropertyValuesClient(connection); + var propertyValues = new UpsertOrganizationCustomPropertyValues + { + RepositoryNames = new List { "repo" }, + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await client.CreateOrUpdate("org", propertyValues); + + connection.Received() + .Patch(Arg.Is(u => u.ToString() == "orgs/org/properties/values"), propertyValues); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationCustomPropertyValuesClient(Substitute.For()); + var propertyValues = new UpsertOrganizationCustomPropertyValues + { + RepositoryNames = new List { "repo" }, + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, propertyValues)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", null)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", new UpsertOrganizationCustomPropertyValues())); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", propertyValues)); + } + } + } +} diff --git a/Octokit.Tests/Clients/RepositoryCustomPropertiesClientTests.cs b/Octokit.Tests/Clients/RepositoryCustomPropertiesClientTests.cs new file mode 100644 index 0000000000..0ab1626fac --- /dev/null +++ b/Octokit.Tests/Clients/RepositoryCustomPropertiesClientTests.cs @@ -0,0 +1,90 @@ +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class RepositoryCustomPropertiesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new RepositoryCustomPropertiesClient(null)); + } + } + + public class GetAllMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryCustomPropertiesClient(connection); + + await client.GetAll("org", "repo"); + + connection.Received() + .Get>(Arg.Is(u => u.ToString() == "repos/org/repo/properties/values"), null); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null, "repo")); + await Assert.ThrowsAsync(() => client.GetAll("org", null)); + + await Assert.ThrowsAsync(() => client.GetAll("", "repo")); + await Assert.ThrowsAsync(() => client.GetAll("org", "")); + } + } + + public class CreateOrUpdateMethod + { + [Fact] + public async Task PatchTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryCustomPropertiesClient(connection); + var propertyValues = new UpsertRepositoryCustomPropertyValues + { + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await client.CreateOrUpdate("org", "repo", propertyValues); + + connection.Received() + .Patch(Arg.Is(u => u.ToString() == "repos/org/repo/properties/values"), propertyValues); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryCustomPropertiesClient(Substitute.For()); + var propertyValues = new UpsertRepositoryCustomPropertyValues + { + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, "repo", propertyValues)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", null, propertyValues)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", "repo", null)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", "repo", new UpsertRepositoryCustomPropertyValues())); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", "repo", propertyValues)); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", "", propertyValues)); + } + } + } +} diff --git a/Octokit.Tests/Models/CustomPropertyValueTests.cs b/Octokit.Tests/Models/CustomPropertyValueTests.cs new file mode 100644 index 0000000000..184203810d --- /dev/null +++ b/Octokit.Tests/Models/CustomPropertyValueTests.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class CustomPropertyValuesTests + { + [Fact] + public void CanBeDeserialized() + { + const string json = @"[ + { + ""property_name"": ""test_ms"", + ""value"": [ + ""option_d"", + ""option_e"" + ] + }, + { + ""property_name"": ""test_ss"", + ""value"": ""option_c"" + }, + { + ""property_name"": ""test_str"", + ""value"": ""hello"" + }, + { + ""property_name"": ""test_tf"", + ""value"": ""unset"" + } +] +"; + var serializer = new SimpleJsonSerializer(); + + var properties = serializer.Deserialize>(json); + + Assert.NotNull(properties); + Assert.Equal(4, properties.Count); + + var testMs = properties[0]; + Assert.Equal("test_ms", testMs.PropertyName); + Assert.Equal(new List { "option_d", "option_e" }, testMs.Values); + + var testSs = properties[1]; + Assert.Equal("test_ss", testSs.PropertyName); + Assert.Equal("option_c", testSs.Value); + Assert.Equal(new List { "option_c" }, testSs.Values); + + var testStr = properties[2]; + Assert.Equal("test_str", testStr.PropertyName); + Assert.Equal("hello", testStr.Value); + + var testTf = properties[3]; + Assert.Equal("test_tf", testTf.PropertyName); + Assert.Equal("unset", testTf.Value); + } + } +} diff --git a/Octokit.Tests/Models/CustomPropertyValueUpdateTests.cs b/Octokit.Tests/Models/CustomPropertyValueUpdateTests.cs new file mode 100644 index 0000000000..c2a853c87e --- /dev/null +++ b/Octokit.Tests/Models/CustomPropertyValueUpdateTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class CustomPropertyValueUpdateTests + { + [Fact] + public void CanSerializeMultiSelect() + { + var expected = "{\"property_name\":\"test_ms\"," + + "\"value\":[\"option_d\",\"option_e\"]}"; + + + var update = new CustomPropertyValueUpdate("test_ms", new List { "option_d", "option_e" }); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeSingleSelect() + { + var expected = "{\"property_name\":\"test_ss\"," + + "\"value\":\"option_c\"}"; + + var update = new CustomPropertyValueUpdate("test_ss", "option_c"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeString() + { + var expected = "{\"property_name\":\"test_str\"," + + "\"value\":\"hello\"}"; + + var update = new CustomPropertyValueUpdate("test_str", "hello"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeTrueFalse() + { + var expected = "{\"property_name\":\"test_tf\"," + + "\"value\":\"true\"}"; + + var update = new CustomPropertyValueUpdate("test_tf", "true"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + } +} diff --git a/Octokit.Tests/Models/OrganizationCustomPropertiesTests.cs b/Octokit.Tests/Models/OrganizationCustomPropertiesTests.cs new file mode 100644 index 0000000000..1261abb5cf --- /dev/null +++ b/Octokit.Tests/Models/OrganizationCustomPropertiesTests.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class OrganizationCustomPropertiesTests + { + [Fact] + public void CanBeDeserialized() + { + const string json = @"[ + { + ""property_name"": ""test_ms"", + ""value_type"": ""multi_select"", + ""required"": true, + ""default_value"": [ + ""option_d"", + ""option_e"" + ], + ""description"": ""multi_select property"", + ""allowed_values"": [ + ""option_a"", + ""option_b"", + ""option_c"", + ""option_d"", + ""option_e"" + ], + ""values_editable_by"": ""org_actors"" + }, + { + ""property_name"": ""test_ss"", + ""value_type"": ""single_select"", + ""required"": true, + ""default_value"": ""option_c"", + ""description"": ""single_select property"", + ""allowed_values"": [ + ""option_a"", + ""option_b"", + ""option_c"", + ""option_d"", + ""option_e"" + ], + ""values_editable_by"": ""org_actors"" + }, + { + ""property_name"": ""test_str"", + ""value_type"": ""string"", + ""required"": false, + ""description"": ""string property"", + ""values_editable_by"": ""org_actors"" + }, + { + ""property_name"": ""test_tf"", + ""value_type"": ""true_false"", + ""required"": true, + ""default_value"": ""unset"", + ""description"": ""true_false property"", + ""values_editable_by"": ""org_actors"" + } +] +"; + var serializer = new SimpleJsonSerializer(); + + var properties = serializer.Deserialize>(json); + Assert.NotNull(properties); + Assert.Equal(4, properties.Count); + + var testMs = properties[0]; + Assert.Equal("test_ms", testMs.PropertyName); + Assert.Equal(CustomPropertyValueType.MultiSelect, testMs.ValueType); + Assert.True(testMs.Required); + Assert.Equal(new List { "option_d", "option_e" }, testMs.DefaultValues); + Assert.Equal("multi_select property", testMs.Description); + Assert.Equal(new List { "option_a", "option_b", "option_c", "option_d", "option_e" }, testMs.AllowedValues); + Assert.Equal(CustomPropertyValuesEditableBy.OrgActors, testMs.ValuesEditableBy); + + var testSs = properties[1]; + Assert.Equal("test_ss", testSs.PropertyName); + Assert.Equal(CustomPropertyValueType.SingleSelect, testSs.ValueType); + Assert.True(testSs.Required); + Assert.Equal("option_c", testSs.DefaultValue); + Assert.Equal(new List { "option_c" }, testSs.DefaultValues); + Assert.Equal("single_select property", testSs.Description); + Assert.Equal(new List { "option_a", "option_b", "option_c", "option_d", "option_e" }, testSs.AllowedValues); + Assert.Equal(CustomPropertyValuesEditableBy.OrgActors, testSs.ValuesEditableBy); + + var testStr = properties[2]; + Assert.Equal("test_str", testStr.PropertyName); + Assert.Equal(CustomPropertyValueType.String, testStr.ValueType); + Assert.False(testStr.Required); + Assert.Equal("string property", testStr.Description); + Assert.Equal(CustomPropertyValuesEditableBy.OrgActors, testStr.ValuesEditableBy); + + var testTf = properties[3]; + Assert.Equal("test_tf", testTf.PropertyName); + Assert.Equal(CustomPropertyValueType.TrueFalse, testTf.ValueType); + Assert.True(testTf.Required); + Assert.Equal("unset", testTf.DefaultValue); + Assert.Equal("true_false property", testTf.Description); + Assert.Equal(CustomPropertyValuesEditableBy.OrgActors, testTf.ValuesEditableBy); + } + } +} diff --git a/Octokit.Tests/Models/OrganizationCustomPropertyUpdateTests.cs b/Octokit.Tests/Models/OrganizationCustomPropertyUpdateTests.cs new file mode 100644 index 0000000000..bb98f61903 --- /dev/null +++ b/Octokit.Tests/Models/OrganizationCustomPropertyUpdateTests.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class OrganizationCustomPropertyUpdateTests + { + [Fact] + public void CanSerializeMultiSelect() + { + var expected = "{\"property_name\":\"test_ms\"," + + "\"value_type\":\"multi_select\"," + + "\"required\":true," + + "\"default_value\":[\"option_d\",\"option_e\"]}"; + + + var update = new OrganizationCustomPropertyUpdate("test_ms", CustomPropertyValueType.MultiSelect, new List { "option_d", "option_e" }); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeSingleSelect() + { + var expected = "{\"property_name\":\"test_ss\"," + + "\"value_type\":\"single_select\"," + + "\"required\":true," + + "\"default_value\":\"option_c\"}"; + + var update = new OrganizationCustomPropertyUpdate("test_ss", CustomPropertyValueType.SingleSelect, "option_c"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeString() + { + var expected = "{\"property_name\":\"test_str\"," + + "\"value_type\":\"string\"," + + "\"required\":true," + + "\"default_value\":\"hello\"}"; + + var update = new OrganizationCustomPropertyUpdate("test_str", CustomPropertyValueType.String, "hello"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeTrueFalse() + { + var expected = "{\"property_name\":\"test_tf\"," + + "\"value_type\":\"true_false\"," + + "\"required\":true," + + "\"default_value\":\"true\"}"; + + var update = new OrganizationCustomPropertyUpdate("test_tf", CustomPropertyValueType.TrueFalse, "true"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + } +} diff --git a/Octokit.Tests/Models/OrganizationCustomPropertyValuesTests.cs b/Octokit.Tests/Models/OrganizationCustomPropertyValuesTests.cs new file mode 100644 index 0000000000..5fc1e8e3ce --- /dev/null +++ b/Octokit.Tests/Models/OrganizationCustomPropertyValuesTests.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class OrganizationCustomPropertyValuesTests + { + [Fact] + public void CanBeDeserialized() + { + const string json = @"[ + { + ""repository_id"": 816170000, + ""repository_name"": ""somerepo"", + ""repository_full_name"": ""contoso/somerepo"", + ""properties"": [ + { + ""property_name"": ""test_ms"", + ""value"": [ + ""option_d"", + ""option_e"" + ] + }, + { + ""property_name"": ""test_ss"", + ""value"": ""option_c"" + }, + { + ""property_name"": ""test_str"", + ""value"": null + }, + { + ""property_name"": ""test_tf"", + ""value"": ""true"" + } + ] + }, + { + ""repository_id"": 813230000, + ""repository_name"": ""entities"", + ""repository_full_name"": ""contoso/entities"", + ""properties"": [ + { + ""property_name"": ""test_ms"", + ""value"": [ + ""option_d"", + ""option_e"" + ] + }, + { + ""property_name"": ""test_ss"", + ""value"": ""option_c"" + }, + { + ""property_name"": ""test_str"", + ""value"": ""hello"" + }, + { + ""property_name"": ""test_tf"", + ""value"": ""unset"" + } + ] + } +] +"; + var serializer = new SimpleJsonSerializer(); + + var properties = serializer.Deserialize>(json); + Assert.NotNull(properties); + Assert.Equal(2, properties.Count); + + var somerepo = properties[0]; + Assert.Equal(816170000, somerepo.RepositoryId); + Assert.Equal("somerepo", somerepo.RepositoryName); + Assert.Equal("contoso/somerepo", somerepo.RepositoryFullName); + Assert.Equal(4, somerepo.Properties.Count); + + var somerepoTestMs = somerepo.Properties[0]; + Assert.Equal("test_ms", somerepoTestMs.PropertyName); + Assert.Equal(new List { "option_d", "option_e" }, somerepoTestMs.Values); + + var somerepoTestSs = somerepo.Properties[1]; + Assert.Equal("test_ss", somerepoTestSs.PropertyName); + Assert.Equal("option_c", somerepoTestSs.Value); + Assert.Equal(new List { "option_c" }, somerepoTestSs.Values); + + var somerepoTestStr = somerepo.Properties[2]; + Assert.Equal("test_str", somerepoTestStr.PropertyName); + Assert.Null(somerepoTestStr.Value); + + var somerepoTestTf = somerepo.Properties[3]; + Assert.Equal("test_tf", somerepoTestTf.PropertyName); + Assert.Equal("true", somerepoTestTf.Value); + + + var entities = properties[1]; + Assert.Equal(813230000, entities.RepositoryId); + Assert.Equal("entities", entities.RepositoryName); + Assert.Equal("contoso/entities", entities.RepositoryFullName); + Assert.Equal(4, entities.Properties.Count); + + var entitiesTestMs = entities.Properties[0]; + Assert.Equal("test_ms", entitiesTestMs.PropertyName); + Assert.Equal(new List { "option_d", "option_e" }, entitiesTestMs.Values); + + var entitiesTestSs = entities.Properties[1]; + Assert.Equal("test_ss", entitiesTestSs.PropertyName); + Assert.Equal("option_c", entitiesTestSs.Value); + Assert.Equal(new List { "option_c" }, entitiesTestSs.Values); + + var entitiesTestStr = entities.Properties[2]; + Assert.Equal("test_str", entitiesTestStr.PropertyName); + Assert.Equal("hello", entitiesTestStr.Value); + + var entitiesTestTf = entities.Properties[3]; + Assert.Equal("test_tf", entitiesTestTf.PropertyName); + Assert.Equal("unset", entitiesTestTf.Value); + } + } +} diff --git a/Octokit.Tests/Models/UpsertOrganizationCustomPropertyTests.cs b/Octokit.Tests/Models/UpsertOrganizationCustomPropertyTests.cs new file mode 100644 index 0000000000..e00c9ed4d9 --- /dev/null +++ b/Octokit.Tests/Models/UpsertOrganizationCustomPropertyTests.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class UpsertOrganizationCustomPropertyTests + { + [Fact] + public void CanSerializeMultiSelect() + { + var expected = "{\"value_type\":\"multi_select\"," + + "\"required\":true," + + "\"default_value\":[\"option_d\",\"option_e\"]}"; + + + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.MultiSelect, new List { "option_d", "option_e" }); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeSingleSelect() + { + var expected = "{\"value_type\":\"single_select\"," + + "\"required\":true," + + "\"default_value\":\"option_c\"}"; + + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.SingleSelect, "option_c"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeString() + { + var expected = "{\"value_type\":\"string\"," + + "\"required\":true," + + "\"default_value\":\"hello\"}"; + + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.String, "hello"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + + [Fact] + public void CanSerializeTrueFalse() + { + var expected = "{\"value_type\":\"true_false\"," + + "\"required\":true," + + "\"default_value\":\"true\"}"; + + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.TrueFalse, "true"); + + var json = new SimpleJsonSerializer().Serialize(update); + + Assert.Equal(expected, json); + } + } +} diff --git a/Octokit.Tests/Reactive/ObservableOrganizationCustomPropertiesClientTests.cs b/Octokit.Tests/Reactive/ObservableOrganizationCustomPropertiesClientTests.cs new file mode 100644 index 0000000000..ced499f031 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservableOrganizationCustomPropertiesClientTests.cs @@ -0,0 +1,169 @@ +using NSubstitute; +using Octokit.Reactive; +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservableOrganizationCustomPropertiesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new ObservableOrganizationCustomPropertiesClient(null)); + } + } + + public class GetAllMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationCustomPropertiesClient(gitHubClient); + + client.GetAll("org"); + + gitHubClient.Received().Organization.CustomProperty.GetAll("org"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableOrganizationCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null).ToTask()); + await Assert.ThrowsAsync(() => client.GetAll("").ToTask()); + } + } + + public class GetMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationCustomPropertiesClient(gitHubClient); + + await client.Get("org", "property"); + + gitHubClient.Received().Organization.CustomProperty.Get("org", "property"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableOrganizationCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Get(null, "property").ToTask()); + await Assert.ThrowsAsync(() => client.Get("org", null).ToTask()); + + await Assert.ThrowsAsync(() => client.Get("", "property").ToTask()); + await Assert.ThrowsAsync(() => client.Get("org", "").ToTask()); + } + } + + public class BatchCreateOrUpdateMethod + { + [Fact] + public void PostsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationCustomPropertiesClient(gitHubClient); + var properties = new UpsertOrganizationCustomProperties + { + Properties = new List + { + new OrganizationCustomPropertyUpdate("name", CustomPropertyValueType.String, "default") + } + }; + + client.CreateOrUpdate("org", properties); + + gitHubClient.Received().Organization.CustomProperty.CreateOrUpdate("org", properties); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableOrganizationCustomPropertiesClient(Substitute.For()); + var properties = new UpsertOrganizationCustomProperties + { + Properties = new List + { + new OrganizationCustomPropertyUpdate("name", CustomPropertyValueType.String, "default") + } + }; + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, properties).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", null).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", new UpsertOrganizationCustomProperties()).ToTask()); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", properties).ToTask()); + } + } + + public class CreateOrUpdateMethod + { + [Fact] + public async Task PostsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationCustomPropertiesClient(gitHubClient); + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.String, "value"); + + await client.CreateOrUpdate("org", "property", update); + + gitHubClient.Received().Organization.CustomProperty.CreateOrUpdate("org", "property", update); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableOrganizationCustomPropertiesClient(Substitute.For()); + + var update = new UpsertOrganizationCustomProperty(CustomPropertyValueType.String, "value"); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, "property", update).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", null, update).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", "property", null).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", "property", new UpsertOrganizationCustomProperty()).ToTask()); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", "property", update).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("owner", "", update).ToTask()); + } + } + + public class DeleteMethod + { + [Fact] + public async Task DeletesTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationCustomPropertiesClient(gitHubClient); + + await client.Delete("org", "property"); + + gitHubClient.Received().Organization.CustomProperty.Delete("org", "property"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableOrganizationCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Delete(null, "property").ToTask()); + await Assert.ThrowsAsync(() => client.Delete("owner", null).ToTask()); + + await Assert.ThrowsAsync(() => client.Delete("", "property").ToTask()); + await Assert.ThrowsAsync(() => client.Delete("owner", "").ToTask()); + } + } + } +} diff --git a/Octokit.Tests/Reactive/ObservableOrganizationCustomPropertyValuesClientTests.cs b/Octokit.Tests/Reactive/ObservableOrganizationCustomPropertyValuesClientTests.cs new file mode 100644 index 0000000000..f96913b1f4 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservableOrganizationCustomPropertyValuesClientTests.cs @@ -0,0 +1,89 @@ +using NSubstitute; +using Octokit.Reactive; +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservableOrganizationCustomPropertyValuesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new ObservableOrganizationCustomPropertyValuesClient(null)); + } + } + + public class GetAllMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationCustomPropertyValuesClient(gitHubClient); + + client.GetAll("org"); + + gitHubClient.Received().Organization.CustomProperty.Values.GetAll("org"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableOrganizationCustomPropertyValuesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null).ToTask()); + + await Assert.ThrowsAsync(() => client.GetAll("").ToTask()); + } + } + + public class CreateOrUpdateMethod + { + [Fact] + public async Task PostsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationCustomPropertyValuesClient(gitHubClient); + var propertyValues = new UpsertOrganizationCustomPropertyValues + { + RepositoryNames = new List { "repo" }, + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await client.CreateOrUpdate("org", propertyValues); + + gitHubClient.Received().Organization.CustomProperty.Values.CreateOrUpdate("org", propertyValues); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableOrganizationCustomPropertyValuesClient(Substitute.For()); + var propertyValues = new UpsertOrganizationCustomPropertyValues + { + RepositoryNames = new List { "repo" }, + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, propertyValues).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", null).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", new UpsertOrganizationCustomPropertyValues()).ToTask()); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", propertyValues).ToTask()); + } + } + } +} diff --git a/Octokit.Tests/Reactive/ObservableRepositoryCustomPropertiesClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoryCustomPropertiesClientTests.cs new file mode 100644 index 0000000000..184e484620 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservableRepositoryCustomPropertiesClientTests.cs @@ -0,0 +1,91 @@ +using NSubstitute; +using Octokit.Reactive; +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservableRepositoryCustomPropertiesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new ObservableRepositoryCustomPropertiesClient(null)); + } + } + + public class GetAllMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryCustomPropertiesClient(gitHubClient); + + client.GetAll("org", "repo"); + + gitHubClient.Received().Repository.CustomProperty.GetAll("org", "repo"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableRepositoryCustomPropertiesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null, "repo").ToTask()); + await Assert.ThrowsAsync(() => client.GetAll("org", null).ToTask()); + + await Assert.ThrowsAsync(() => client.GetAll("", "repo").ToTask()); + await Assert.ThrowsAsync(() => client.GetAll("org", "").ToTask()); + } + } + + public class CreateOrUpdateMethod + { + [Fact] + public async Task PostsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryCustomPropertiesClient(gitHubClient); + var propertyValues = new UpsertRepositoryCustomPropertyValues + { + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await client.CreateOrUpdate("org", "repo", propertyValues); + + gitHubClient.Received().Repository.CustomProperty.CreateOrUpdate("org", "repo", propertyValues); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableRepositoryCustomPropertiesClient(Substitute.For()); + var propertyValues = new UpsertRepositoryCustomPropertyValues + { + Properties = new List + { + new CustomPropertyValueUpdate("name", "value") + } + }; + + await Assert.ThrowsAsync(() => client.CreateOrUpdate(null, "repo", propertyValues).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", null, propertyValues).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", "repo", null).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", "repo", new UpsertRepositoryCustomPropertyValues()).ToTask()); + + await Assert.ThrowsAsync(() => client.CreateOrUpdate("", "repo", propertyValues).ToTask()); + await Assert.ThrowsAsync(() => client.CreateOrUpdate("org", "", propertyValues).ToTask()); + } + } + } +} diff --git a/Octokit/Clients/IOrganizationCustomPropertiesClient.cs b/Octokit/Clients/IOrganizationCustomPropertiesClient.cs new file mode 100644 index 0000000000..8e5a866f60 --- /dev/null +++ b/Octokit/Clients/IOrganizationCustomPropertiesClient.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Organization Custom Properties API. + /// + /// + /// See Custom Properties API documentation for more information. + /// + public interface IOrganizationCustomPropertiesClient + { + /// + /// Get all custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + [ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 15/06/2024)")] + Task> GetAll(string org); + + /// + /// Get a single custom property by name. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + Task Get(string org, string propertyName); + + /// + /// Create new or update existing custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom properties to create or update + Task> CreateOrUpdate(string org, UpsertOrganizationCustomProperties properties); + + /// + /// Create new or update existing custom property for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + /// The custom property to create or update + Task CreateOrUpdate(string org, string propertyName, UpsertOrganizationCustomProperty property); + + /// + /// Removes a custom property that is defined for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + Task Delete(string org, string propertyName); + + /// + /// A client for GitHub's Organization Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + IOrganizationCustomPropertyValuesClient Values { get; } + } +} diff --git a/Octokit/Clients/IOrganizationCustomPropertyValuesClient.cs b/Octokit/Clients/IOrganizationCustomPropertyValuesClient.cs new file mode 100644 index 0000000000..62bfb00e3c --- /dev/null +++ b/Octokit/Clients/IOrganizationCustomPropertyValuesClient.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Organization Custom Property Values API. + /// + /// + /// See Custom Properties API documentation for more information. + /// + public interface IOrganizationCustomPropertyValuesClient + { + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + Task> GetAll(string org); + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Options for changing the API response + Task> GetAll(string org, ApiOptions options); + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Finds repositories in the organization with a query containing one or more search keywords and qualifiers. + [ExcludeFromPaginationApiOptionsConventionTest("This API call uses the OrganizationCustomPropertyValuesRequest parameter for pagination")] + Task> GetAll(string org, OrganizationCustomPropertyValuesRequest repositoryQuery); + + /// + /// Create new or update existing custom property values for repositories an organization. + /// Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// A maximum of 30 repositories can be updated in a single request. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom property values to create or update + Task CreateOrUpdate(string org, UpsertOrganizationCustomPropertyValues propertyValues); + } +} diff --git a/Octokit/Clients/IOrganizationsClient.cs b/Octokit/Clients/IOrganizationsClient.cs index d87d69387d..d1f86bd756 100644 --- a/Octokit/Clients/IOrganizationsClient.cs +++ b/Octokit/Clients/IOrganizationsClient.cs @@ -40,6 +40,11 @@ public interface IOrganizationsClient /// IOrganizationActionsClient Actions { get; } + /// + /// Returns a client to manage organization custom properties. + /// + IOrganizationCustomPropertiesClient CustomProperty { get; } + /// /// Returns the specified . /// diff --git a/Octokit/Clients/IRepositoriesClient.cs b/Octokit/Clients/IRepositoriesClient.cs index 67f318e12b..98daa6a9be 100644 --- a/Octokit/Clients/IRepositoriesClient.cs +++ b/Octokit/Clients/IRepositoriesClient.cs @@ -53,6 +53,14 @@ public interface IRepositoriesClient /// IRepositoryCommentsClient Comment { get; } + /// + /// Client for managing custom property values on a repository. + /// + /// + /// See the Repository Custom Properties API documentation for more information. + /// + IRepositoryCustomPropertiesClient CustomProperty { get; } + /// /// Client for managing deploy keys in a repository. /// diff --git a/Octokit/Clients/IRepositoryCustomPropertiesClient.cs b/Octokit/Clients/IRepositoryCustomPropertiesClient.cs new file mode 100644 index 0000000000..60ff86e071 --- /dev/null +++ b/Octokit/Clients/IRepositoryCustomPropertiesClient.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Repository Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + public interface IRepositoryCustomPropertiesClient + { + /// + /// Get all custom property values for a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + [ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 15/06/2024)")] + Task> GetAll(string owner, string repoName); + + /// + /// Create new or update existing custom property values for a repository. Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The custom property values to create or update + Task CreateOrUpdate(string owner, string repoName, UpsertRepositoryCustomPropertyValues propertyValues); + } +} diff --git a/Octokit/Clients/OrganizationCustomPropertiesClient.cs b/Octokit/Clients/OrganizationCustomPropertiesClient.cs new file mode 100644 index 0000000000..0e47e65e12 --- /dev/null +++ b/Octokit/Clients/OrganizationCustomPropertiesClient.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + public class OrganizationCustomPropertiesClient : ApiClient, IOrganizationCustomPropertiesClient + { + /// + /// Initializes a new GitHub Organization Custom Properties API client. + /// + /// An API connection. + public OrganizationCustomPropertiesClient(IApiConnection apiConnection) + : base(apiConnection) + { + Values = new OrganizationCustomPropertyValuesClient(apiConnection); + } + + /// + /// Get all custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + [ManualRoute("GET", "orgs/{org}/properties/schema")] + public Task> GetAll(string org) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + var url = ApiUrls.OrganizationCustomProperties(org); + + return ApiConnection.Get>(url, null); + } + + /// + /// Get a single custom property by name. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + [ManualRoute("GET", "orgs/{org}/properties/schema/{propertyName}")] + public Task Get(string org, string propertyName) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(propertyName, nameof(propertyName)); + + var url = ApiUrls.OrganizationCustomProperty(org, propertyName); + + return ApiConnection.Get(url); + } + + /// + /// Create new or update existing custom properties for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom properties to create or update + [ManualRoute("PATCH", "orgs/{org}/properties/schema")] + public Task> CreateOrUpdate(string org, UpsertOrganizationCustomProperties properties) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(properties, nameof(properties)); + Ensure.ArgumentNotNullOrEmptyEnumerable(properties.Properties, nameof(properties.Properties)); + + var url = ApiUrls.OrganizationCustomProperties(org); + + return ApiConnection.Patch>(url, properties); + } + + /// + /// Create new or update existing custom property for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + /// The custom property to create or update + [ManualRoute("PUT", "orgs/{org}/properties/schema/{propertyName}")] + public Task CreateOrUpdate(string org, string propertyName, UpsertOrganizationCustomProperty property) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(propertyName, nameof(propertyName)); + Ensure.ArgumentNotNull(property, nameof(property)); + Ensure.ArgumentNotNullOrDefault(property.ValueType, nameof(property.ValueType)); + + var url = ApiUrls.OrganizationCustomProperty(org, propertyName); + + return ApiConnection.Put(url, property); + } + + /// + /// Removes a custom property that is defined for an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The name of the custom property + [ManualRoute("DELETE", "orgs/{org}/properties/schema/{propertyName}")] + public Task Delete(string org, string propertyName) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(propertyName, nameof(propertyName)); + + var url = ApiUrls.OrganizationCustomProperty(org, propertyName); + + return ApiConnection.Delete(url); + } + + /// + /// A client for GitHub's Organization Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + public IOrganizationCustomPropertyValuesClient Values { get; private set; } + } +} diff --git a/Octokit/Clients/OrganizationCustomPropertyValuesClient.cs b/Octokit/Clients/OrganizationCustomPropertyValuesClient.cs new file mode 100644 index 0000000000..d3aa2c98d1 --- /dev/null +++ b/Octokit/Clients/OrganizationCustomPropertyValuesClient.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + public class OrganizationCustomPropertyValuesClient : ApiClient, IOrganizationCustomPropertyValuesClient + { + /// + /// Initializes a new GitHub Organization Custom Property Values API client. + /// + /// An API connection. + public OrganizationCustomPropertyValuesClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Thrown when a general API error occurs. + [ManualRoute("GET", "orgs/{org}/properties/values")] + public Task> GetAll(string org) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return GetAll(org, new OrganizationCustomPropertyValuesRequest()); + } + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Options for changing the API response + /// Thrown when a general API error occurs. + [ManualRoute("GET", "orgs/{org}/properties/values")] + public Task> GetAll(string org, ApiOptions options) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(options, nameof(options)); + + var url = ApiUrls.OrganizationCustomPropertyValues(org); + + return ApiConnection.GetAll(url, options); + } + + /// + /// Get all custom property values for repositories an organization. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// Finds repositories in the organization with a query containing one or more search keywords and qualifiers. + /// Thrown when a general API error occurs. + [ManualRoute("GET", "orgs/{org}/properties/values")] + public Task> GetAll(string org, OrganizationCustomPropertyValuesRequest repositoryQuery) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(repositoryQuery, nameof(repositoryQuery)); + + var url = ApiUrls.OrganizationCustomPropertyValues(org); + + return ApiConnection.GetAll(url, repositoryQuery.Parameters); + } + + /// + /// Create new or update existing custom property values for repositories an organization. + /// Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// A maximum of 30 repositories can be updated in a single request. + /// + /// + /// See the API documentation for more information. + /// + /// The name of the organization + /// The custom property values to create or update + /// Thrown when a general API error occurs. + [ManualRoute("PATCH", "orgs/{org}/properties/values")] + public Task CreateOrUpdate(string org, UpsertOrganizationCustomPropertyValues propertyValues) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(propertyValues, nameof(propertyValues)); + Ensure.ArgumentNotNullOrEmptyEnumerable(propertyValues.Properties, nameof(propertyValues.Properties)); + + var url = ApiUrls.OrganizationCustomPropertyValues(org); + + return ApiConnection.Patch(url, propertyValues); + } + } +} diff --git a/Octokit/Clients/OrganizationsClient.cs b/Octokit/Clients/OrganizationsClient.cs index 57abb1e716..526a30686b 100644 --- a/Octokit/Clients/OrganizationsClient.cs +++ b/Octokit/Clients/OrganizationsClient.cs @@ -23,6 +23,7 @@ public OrganizationsClient(IApiConnection apiConnection) : base(apiConnection) Hook = new OrganizationHooksClient(apiConnection); OutsideCollaborator = new OrganizationOutsideCollaboratorsClient(apiConnection); Actions = new OrganizationActionsClient(apiConnection); + CustomProperty = new OrganizationCustomPropertiesClient(apiConnection); } /// @@ -45,6 +46,11 @@ public OrganizationsClient(IApiConnection apiConnection) : base(apiConnection) /// public IOrganizationOutsideCollaboratorsClient OutsideCollaborator { get; private set; } + /// + /// Returns a client to manage organization custom properties. + /// + public IOrganizationCustomPropertiesClient CustomProperty { get; private set; } + /// /// Returns the specified . /// diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index 70ae3b8037..184da6614b 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -33,6 +33,7 @@ public RepositoriesClient(IApiConnection apiConnection) : base(apiConnection) PullRequest = new PullRequestsClient(apiConnection); Comment = new RepositoryCommentsClient(apiConnection); Commit = new RepositoryCommitsClient(apiConnection); + CustomProperty = new RepositoryCustomPropertiesClient(apiConnection); Release = new ReleasesClient(apiConnection); DeployKeys = new RepositoryDeployKeysClient(apiConnection); Merging = new MergingClient(apiConnection); @@ -569,6 +570,14 @@ public Task> GetAllForOrg(string organization, ApiOpti /// public IRepositoryCommitsClient Commit { get; private set; } + /// + /// Client for GitHub's Repository Custom Property Values API. + /// + /// + /// See the Repository Custom Properties API documentation for more details. + /// + public IRepositoryCustomPropertiesClient CustomProperty { get; private set; } + /// /// Access GitHub's Releases API. /// diff --git a/Octokit/Clients/RepositoryCustomPropertiesClient.cs b/Octokit/Clients/RepositoryCustomPropertiesClient.cs new file mode 100644 index 0000000000..b86a03ec59 --- /dev/null +++ b/Octokit/Clients/RepositoryCustomPropertiesClient.cs @@ -0,0 +1,66 @@ +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Octokit +{ + /// + /// A client for GitHub's Repository Custom Property Values API. + /// + /// + /// See the Custom Properties API documentation for more information. + /// + public class RepositoryCustomPropertiesClient : ApiClient, IRepositoryCustomPropertiesClient + { + /// + /// Initializes a new GitHub repository custom property values API client. + /// + /// An API connection. + public RepositoryCustomPropertiesClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// Get all custom property values for a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + /// Thrown when a general API error occurs. + [ManualRoute("GET", "/repos/{owner}/{repo}/properties/values")] + public Task> GetAll(string owner, string repoName) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repoName, nameof(repoName)); + + var url = ApiUrls.RepositoryCustomPropertyValues(owner, repoName); + + return ApiConnection.Get>(url, null); + } + + /// + /// Create new or update existing custom property values for a repository. Using a value of null for a custom property will remove or 'unset' the property value from the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The custom property values to create or update + /// Thrown when a general API error occurs. + [ManualRoute("PATCH", "/repos/{owner}/{repo}/properties/values")] + public Task CreateOrUpdate(string owner, string repoName, UpsertRepositoryCustomPropertyValues propertyValues) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repoName, nameof(repoName)); + Ensure.ArgumentNotNull(propertyValues, nameof(propertyValues)); + Ensure.ArgumentNotNullOrEmptyEnumerable(propertyValues.Properties, nameof(propertyValues.Properties)); + + var url = ApiUrls.RepositoryCustomPropertyValues(owner, repoName); + + return ApiConnection.Patch(url, propertyValues); + } + } +} diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 44eefc029e..3e147ac7b8 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -78,6 +78,41 @@ public static Uri OrganizationRepositories(string organization) return "orgs/{0}/repos".FormatUri(organization); } + /// + /// Returns the that returns all of the custom properties for the specified organization in + /// response to a GET request. A PATCH to this URL updates the custom properties for the organization. + /// + /// The name of the organization + /// + public static Uri OrganizationCustomProperties(string organization) + { + return "orgs/{0}/properties/schema".FormatUri(organization); + } + + /// + /// Returns the that returns a custom property for the specified organization in + /// response to a GET request. A PUT to this URL updates the custom property for the organization. + /// + /// The name of the organization + /// The name of the property + /// + public static Uri OrganizationCustomProperty(string organization, string property) + { + return "orgs/{0}/properties/schema/{1}".FormatUri(organization, property); + } + + /// + /// Returns the that returns a custom property values for repositories in the + /// specified organization in response to a GET request. A PATCH to this URL updates the custom property + /// values for specified repositories in the organization. + /// + /// The name of the organization + /// + public static Uri OrganizationCustomPropertyValues(string organization) + { + return "orgs/{0}/properties/values".FormatUri(organization); + } + /// /// Returns the that returns all of the secrets for the specified organization in /// response to a GET request. @@ -4618,6 +4653,17 @@ public static Uri CheckSuitePreferences(string owner, string repo) return "repos/{0}/{1}/check-suites/preferences".FormatUri(owner, repo); } + /// + /// Returns the that handles the repository custom property values for the repository + /// + /// The owner of the repo + /// The name of the repo + /// The that handles the repository secrets for the repository + public static Uri RepositoryCustomPropertyValues(string owner, string repo) + { + return "repos/{0}/{1}/properties/values".FormatUri(owner, repo); + } + /// /// Returns the that handles the repository secrets for the repository /// diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 9d729947e9..929de72a65 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -460,6 +460,20 @@ public Task Patch(Uri uri) return Connection.Patch(uri); } + /// + /// Updates the API resource at the specified URI. + /// + /// URI of the API resource to patch + /// Object that describes the API resource; this will be serialized and used as the request's body + /// A for the request's execution. + public Task Patch(Uri uri, object data) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + Ensure.ArgumentNotNull(data, nameof(data)); + + return Connection.Patch(uri, data); + } + /// /// Updates the API resource at the specified URI. /// @@ -474,6 +488,22 @@ public Task Patch(Uri uri, string accepts) return Connection.Patch(uri, accepts); } + /// + /// Updates the API resource at the specified URI. + /// + /// URI of the API resource to patch + /// Object that describes the API resource; this will be serialized and used as the request's body + /// Accept header to use for the API request + /// A for the request's execution. + public Task Patch(Uri uri, object data, string accepts) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + Ensure.ArgumentNotNull(data, nameof(data)); + Ensure.ArgumentNotNull(accepts, nameof(accepts)); + + return Connection.Patch(uri, data, accepts); + } + /// /// Updates the API resource at the specified URI. /// diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 11a67d4502..091af09ca9 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -498,6 +498,21 @@ public async Task Patch(Uri uri) return response.HttpResponse.StatusCode; } + /// + /// Performs an asynchronous HTTP PATCH request. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// representing the received HTTP response + public async Task Patch(Uri uri, object body) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + Ensure.ArgumentNotNull(body, nameof(body)); + + var response = await SendData(uri, new HttpMethod("PATCH"), body, null, null, CancellationToken.None).ConfigureAwait(false); + return response.HttpResponse.StatusCode; + } + /// /// Performs an asynchronous HTTP PATCH request. /// @@ -513,6 +528,23 @@ public async Task Patch(Uri uri, string accepts) return response.HttpResponse.StatusCode; } + /// + /// Performs an asynchronous HTTP PATCH request. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// Specifies accept response media type + /// representing the received HTTP response + public async Task Patch(Uri uri, object body, string accepts) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + Ensure.ArgumentNotNull(body, nameof(body)); + Ensure.ArgumentNotNull(accepts, nameof(accepts)); + + var response = await SendData(uri, new HttpMethod("PATCH"), body, accepts, null, CancellationToken.None).ConfigureAwait(false); + return response.HttpResponse.StatusCode; + } + /// /// Performs an asynchronous HTTP PUT request that expects an empty response. /// diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index da4a541f1c..3888f5883a 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -306,6 +306,14 @@ public interface IApiConnection /// A for the request's execution. Task Patch(Uri uri); + /// + /// Updates the API resource at the specified URI. + /// + /// URI of the API resource to patch + /// Object that describes the API resource; this will be serialized and used as the request's body + /// A for the request's execution. + Task Patch(Uri uri, object data); + /// /// Updates the API resource at the specified URI. /// @@ -314,6 +322,15 @@ public interface IApiConnection /// A for the request's execution. Task Patch(Uri uri, string accepts); + /// + /// Updates the API resource at the specified URI. + /// + /// URI of the API resource to patch + /// Object that describes the API resource; this will be serialized and used as the request's body + /// Accept header to use for the API request + /// A for the request's execution. + Task Patch(Uri uri, object data, string accepts); + /// /// Updates the API resource at the specified URI. /// diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index c9f8daa848..4e75a10975 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -117,6 +117,14 @@ public interface IConnection : IApiInfoProvider /// representing the received HTTP response Task Patch(Uri uri); + /// + /// Performs an asynchronous HTTP PATCH request. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// representing the received HTTP response + Task Patch(Uri uri, object body); + /// /// Performs an asynchronous HTTP PATCH request. /// @@ -125,6 +133,15 @@ public interface IConnection : IApiInfoProvider /// representing the received HTTP response Task Patch(Uri uri, string accepts); + /// + /// Performs an asynchronous HTTP PATCH request. + /// + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// Specifies accept response media type + /// representing the received HTTP response + Task Patch(Uri uri, object body, string accepts); + /// /// Performs an asynchronous HTTP PATCH request. /// Attempts to map the response body to an object of type diff --git a/Octokit/Models/Request/CustomPropertyValueUpdate.cs b/Octokit/Models/Request/CustomPropertyValueUpdate.cs new file mode 100644 index 0000000000..aa5c6ed8a5 --- /dev/null +++ b/Octokit/Models/Request/CustomPropertyValueUpdate.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Custom property name and associated value + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class CustomPropertyValueUpdate + { + public CustomPropertyValueUpdate() { } + + public CustomPropertyValueUpdate(string propertyName, string value) + { + PropertyName = propertyName; + Value = value; + } + + public CustomPropertyValueUpdate(string propertyName, IReadOnlyList value) + { + PropertyName = propertyName; + Value = value; + } + + /// + /// The name of the property + /// + public string PropertyName { get; set; } + + /// + /// The value assigned to the property + /// + [SerializeNull] + [Parameter(Key = "value")] + public object Value { get; private set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "PropertyName: {0}", PropertyName); + } +} diff --git a/Octokit/Models/Request/OrganizationCustomPropertyUpdate.cs b/Octokit/Models/Request/OrganizationCustomPropertyUpdate.cs new file mode 100644 index 0000000000..4e51fcbba5 --- /dev/null +++ b/Octokit/Models/Request/OrganizationCustomPropertyUpdate.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OrganizationCustomPropertyUpdate + { + public OrganizationCustomPropertyUpdate() { } + + public OrganizationCustomPropertyUpdate(string propertyName, CustomPropertyValueType valueType, string defaultValue) + { + PropertyName = propertyName; + ValueType = valueType; + Required = true; + DefaultValue = defaultValue; + } + + public OrganizationCustomPropertyUpdate(string propertyName, CustomPropertyValueType valueType, IReadOnlyList defaultValue) + { + PropertyName = propertyName; + ValueType = valueType; + Required = true; + DefaultValue = defaultValue; + } + + /// + /// The name of the property + /// + public string PropertyName { get; set; } + + /// + /// The type of the value for the property + /// + public StringEnum ValueType { get; set; } + + /// + /// Whether the property is required + /// + public bool Required { get; set; } + + /// + /// Default value of the property + /// + public object DefaultValue { get; private set; } + + /// + /// Short description of the property + /// + public string Description { get; set; } + + /// + /// An ordered list of the allowed values of the property. + /// The property can have up to 200 allowed values. + /// + public IEnumerable AllowedValues { get; set; } + + /// + /// Who can edit the values of the property + /// + public StringEnum? ValuesEditableBy { get; set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "PropertyName: {0}", PropertyName); + } +} diff --git a/Octokit/Models/Request/OrganizationCustomPropertyValuesRequest.cs b/Octokit/Models/Request/OrganizationCustomPropertyValuesRequest.cs new file mode 100644 index 0000000000..3dc7271712 --- /dev/null +++ b/Octokit/Models/Request/OrganizationCustomPropertyValuesRequest.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OrganizationCustomPropertyValuesRequest : SearchRepositoriesRequest + { + /// + /// Initializes a new instance of the class. + /// + public OrganizationCustomPropertyValuesRequest() : base() + { } + + /// + /// Initializes a new instance of the class. + /// + public OrganizationCustomPropertyValuesRequest(string term) + : base(term) + { } + + public override string Sort => null; + + /// + /// Get the query parameters that will be appending onto the search + /// + public new IDictionary Parameters + { + get + { + var parameters = base.Parameters; + + // Remove the default sort and order parameters as they are not supported by the API + parameters.Remove("order"); + parameters.Remove("sort"); + + // Replace the default query parameter "q" with the custom query parameter + var query = parameters["q"]; + + if (!string.IsNullOrWhiteSpace(query)) + { + parameters.Add("repository_query", query); + } + + parameters.Remove("q"); + + return parameters; + } + } + } +} diff --git a/Octokit/Models/Request/UpsertOrganizationCustomProperties.cs b/Octokit/Models/Request/UpsertOrganizationCustomProperties.cs new file mode 100644 index 0000000000..2cd47fbaee --- /dev/null +++ b/Octokit/Models/Request/UpsertOrganizationCustomProperties.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Used to create or update custom property values for a repository + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class UpsertOrganizationCustomProperties + { + public UpsertOrganizationCustomProperties() { } + + public UpsertOrganizationCustomProperties(List properties) + { + Properties = properties; + } + + /// + /// List of organization custom properties + /// + /// + /// See the API documentation for more information. + /// + [Parameter(Value = "properties")] + public List Properties { get; set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Count: {0}", Properties?.Count); + } + } + } +} diff --git a/Octokit/Models/Request/UpsertOrganizationCustomProperty.cs b/Octokit/Models/Request/UpsertOrganizationCustomProperty.cs new file mode 100644 index 0000000000..ac0b042eb4 --- /dev/null +++ b/Octokit/Models/Request/UpsertOrganizationCustomProperty.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + /// + /// Used to create or update a custom property value for a repository + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class UpsertOrganizationCustomProperty + { + public UpsertOrganizationCustomProperty() { } + + public UpsertOrganizationCustomProperty(CustomPropertyValueType valueType) + { + ValueType = valueType; + } + + public UpsertOrganizationCustomProperty(CustomPropertyValueType valueType, string defaultValue) + { + ValueType = valueType; + Required = true; + DefaultValue = defaultValue; + } + + public UpsertOrganizationCustomProperty(CustomPropertyValueType valueType, IReadOnlyList defaultValue) + { + ValueType = valueType; + Required = true; + DefaultValue = defaultValue; + } + + /// + /// The type of the value for the property + /// + public StringEnum ValueType { get; set; } + + /// + /// Whether the property is required + /// + public bool Required { get; set; } + + /// + /// Default value of the property + /// + public object DefaultValue { get; private set; } + + /// + /// Short description of the property + /// + public string Description { get; set; } + + /// + /// An ordered list of the allowed values of the property. + /// The property can have up to 200 allowed values. + /// + public IEnumerable AllowedValues { get; set; } + + /// + /// Who can edit the values of the property + /// + public StringEnum? ValuesEditableBy { get; set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "ValueType: {0}", ValueType.DebuggerDisplay); + } +} diff --git a/Octokit/Models/Request/UpsertOrganizationCustomPropertyValues.cs b/Octokit/Models/Request/UpsertOrganizationCustomPropertyValues.cs new file mode 100644 index 0000000000..d1330367e1 --- /dev/null +++ b/Octokit/Models/Request/UpsertOrganizationCustomPropertyValues.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Used to create or update custom property values for organization repositories + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class UpsertOrganizationCustomPropertyValues + { + public UpsertOrganizationCustomPropertyValues() { } + + public UpsertOrganizationCustomPropertyValues(List repositoryNames, List properties) + { + RepositoryNames = repositoryNames; + Properties = properties; + } + + /// + /// List of repository names that should create or update custom property values + /// + /// + /// See the API documentation for more information. + /// + [Parameter(Value = "repository_names")] + public List RepositoryNames { get; set; } + + /// + /// List of organization custom properties + /// + /// + /// See the API documentation for more information. + /// + [Parameter(Value = "properties")] + public List Properties { get; set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Count: {0}", Properties?.Count); + } + } + } +} diff --git a/Octokit/Models/Request/UpsertRepositoryCustomPropertyValues.cs b/Octokit/Models/Request/UpsertRepositoryCustomPropertyValues.cs new file mode 100644 index 0000000000..5b0922a701 --- /dev/null +++ b/Octokit/Models/Request/UpsertRepositoryCustomPropertyValues.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Used to create or update custom property values for a repository + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class UpsertRepositoryCustomPropertyValues + { + /// + /// List of custom property names and associated values + /// + /// + /// See the API documentation for more information. + /// + [Parameter(Value = "properties")] + public List Properties { get; set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Count: {0}", Properties?.Count); + } + } + } +} diff --git a/Octokit/Models/Response/CustomPropertyValue.cs b/Octokit/Models/Response/CustomPropertyValue.cs new file mode 100644 index 0000000000..7663833eaf --- /dev/null +++ b/Octokit/Models/Response/CustomPropertyValue.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Custom property name and associated value + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [ExcludeFromCtorWithAllPropertiesConventionTest(nameof(Values))] + public class CustomPropertyValue + { + public CustomPropertyValue() { } + + public CustomPropertyValue(string propertyName, object value) + { + PropertyName = propertyName; + this.value = value; + } + + /// + /// The name of the property + /// + [Parameter(Key = "property_name")] + public string PropertyName { get; private set; } + + + [Parameter(Key = "value")] + public object value { get; private set; } + + /// + /// The value assigned to the property + /// + public string Value { + get { + if (value is null) + { + return null; + } + if (value is string stringValue) + { + return stringValue; + } + else if (value is JsonArray stringValues) + { + return "[" + string.Join(",", stringValues.ConvertAll(x => x.ToString())) + "]"; + } + + return null; + } + } + + /// + /// The array of values assigned to the property + /// + public IReadOnlyList Values { + get { + if (value is null) + { + return null; + } + else if (value is string stringValue) + { + return new List { stringValue }; + } + else if (value is JsonArray stringValues) + { + return stringValues.ConvertAll(x => x.ToString()); + } + + return null; + } + } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "PropertyName: {0}", PropertyName); + } +} diff --git a/Octokit/Models/Response/CustomPropertyValueType.cs b/Octokit/Models/Response/CustomPropertyValueType.cs new file mode 100644 index 0000000000..6447b75a30 --- /dev/null +++ b/Octokit/Models/Response/CustomPropertyValueType.cs @@ -0,0 +1,16 @@ +using Octokit.Internal; + +namespace Octokit +{ + public enum CustomPropertyValueType + { + [Parameter(Value = "string")] + String, + [Parameter(Value = "single_select")] + SingleSelect, + [Parameter(Value = "multi_select")] + MultiSelect, + [Parameter(Value = "true_false")] + TrueFalse, + } +} diff --git a/Octokit/Models/Response/CustomPropertyValuesEditableBy.cs b/Octokit/Models/Response/CustomPropertyValuesEditableBy.cs new file mode 100644 index 0000000000..5dc074a7b9 --- /dev/null +++ b/Octokit/Models/Response/CustomPropertyValuesEditableBy.cs @@ -0,0 +1,12 @@ +using Octokit.Internal; + +namespace Octokit +{ + public enum CustomPropertyValuesEditableBy + { + [Parameter(Value = "org_actors")] + OrgActors, + [Parameter(Value = "org_and_repo_actors")] + OrgAndRepoActors, + } +} diff --git a/Octokit/Models/Response/OrganizationCustomProperty.cs b/Octokit/Models/Response/OrganizationCustomProperty.cs new file mode 100644 index 0000000000..272eb08c17 --- /dev/null +++ b/Octokit/Models/Response/OrganizationCustomProperty.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [ExcludeFromCtorWithAllPropertiesConventionTest(nameof(DefaultValues))] + public class OrganizationCustomProperty + { + public OrganizationCustomProperty() { } + + public OrganizationCustomProperty(string propertyName, CustomPropertyValueType valueType, bool required, object defaultValue, string description, IReadOnlyList allowedValues, CustomPropertyValuesEditableBy? valuesEditableBy) + { + PropertyName = propertyName; + ValueType = valueType; + Required = required; + this.defaultValue = defaultValue; + Description = description; + AllowedValues = allowedValues; + ValuesEditableBy = valuesEditableBy; + } + + [Parameter(Key = "default_value")] + public object defaultValue { get; private set; } + + /// + /// The name of the property + /// + public string PropertyName { get; private set; } + + /// + /// The type of the value for the property + /// + public StringEnum? ValueType { get; private set; } + + /// + /// Whether the property is required + /// + public bool Required { get; private set; } + + /// + /// Default value of the property + /// + public string DefaultValue { + get { + if (defaultValue is null) + { + return null; + } + if (defaultValue is string stringValue) + { + return stringValue; + } + else if (defaultValue is JsonArray stringValues) + { + return "[" + string.Join(",", stringValues.ConvertAll(x => x.ToString())) + "]"; + } + + return null; + } + } + + /// + /// Default values of the property + /// + public IReadOnlyList DefaultValues { + get { + if (defaultValue is null) + { + return null; + } + else if (defaultValue is string stringValue) + { + return new List { stringValue }; + } + else if (defaultValue is JsonArray stringValues) + { + return stringValues.ConvertAll(x => x.ToString()); + } + + return null; + } + } + + /// + /// Short description of the property + /// + public string Description { get; private set; } + + /// + /// An ordered list of the allowed values of the property. + /// The property can have up to 200 allowed values. + /// + public IReadOnlyList AllowedValues { get; private set; } + + /// + /// Who can edit the values of the property + /// + public StringEnum? ValuesEditableBy { get; private set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "PropertyName: {0}", PropertyName); + } +} diff --git a/Octokit/Models/Response/OrganizationCustomPropertyValues.cs b/Octokit/Models/Response/OrganizationCustomPropertyValues.cs new file mode 100644 index 0000000000..30b0fb6e4d --- /dev/null +++ b/Octokit/Models/Response/OrganizationCustomPropertyValues.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + /// + /// List of custom property values for a repository + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OrganizationCustomPropertyValues + { + public OrganizationCustomPropertyValues() { } + + public OrganizationCustomPropertyValues(long repositoryId, string repositoryName, string repositoryFullName, IReadOnlyList properties) + { + RepositoryId = repositoryId; + RepositoryName = repositoryName; + RepositoryFullName = repositoryFullName; + Properties = properties; + } + + /// + /// The repository Id + /// + public long RepositoryId { get; private set; } + + /// + /// The name of the repository + /// + public string RepositoryName { get; private set; } + + /// + /// The full name of the repository (owner/repo) + /// + public string RepositoryFullName { get; private set; } + + /// + /// List of custom property names and associated values + /// + public IReadOnlyList Properties { get; private set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "RepositoryFullName: {0}", RepositoryFullName); + } +} diff --git a/Octokit/Models/Response/Repository.cs b/Octokit/Models/Response/Repository.cs index 91ee81c041..7075c302ad 100644 --- a/Octokit/Models/Response/Repository.cs +++ b/Octokit/Models/Response/Repository.cs @@ -17,7 +17,7 @@ public Repository(long id) Id = id; } - public Repository(string url, string htmlUrl, string cloneUrl, string gitUrl, string sshUrl, string svnUrl, string mirrorUrl, string archiveUrl, long id, string nodeId, User owner, string name, string fullName, bool isTemplate, 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 hasDiscussions, bool hasIssues, bool hasWiki, bool hasDownloads, bool hasPages, int subscribersCount, long size, bool? allowRebaseMerge, bool? allowSquashMerge, bool? allowMergeCommit, bool archived, int watchersCount, bool? deleteBranchOnMerge, RepositoryVisibility visibility, IEnumerable topics, bool? allowAutoMerge, bool? allowUpdateBranch, bool? webCommitSignoffRequired) + public Repository(string url, string htmlUrl, string cloneUrl, string gitUrl, string sshUrl, string svnUrl, string mirrorUrl, string archiveUrl, long id, string nodeId, User owner, string name, string fullName, bool isTemplate, 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 hasDiscussions, bool hasIssues, bool hasWiki, bool hasDownloads, bool hasPages, int subscribersCount, long size, bool? allowRebaseMerge, bool? allowSquashMerge, bool? allowMergeCommit, bool archived, int watchersCount, bool? deleteBranchOnMerge, RepositoryVisibility visibility, IEnumerable topics, bool? allowAutoMerge, bool? allowUpdateBranch, bool? webCommitSignoffRequired, IReadOnlyDictionary customProperties) { Url = url; HtmlUrl = htmlUrl; @@ -69,6 +69,7 @@ public Repository(string url, string htmlUrl, string cloneUrl, string gitUrl, st AllowAutoMerge = allowAutoMerge; AllowUpdateBranch = allowUpdateBranch; WebCommitSignoffRequired = webCommitSignoffRequired; + CustomProperties = customProperties; } public string Url { get; private set; } @@ -170,6 +171,8 @@ public Repository(string url, string htmlUrl, string cloneUrl, string gitUrl, st public bool? WebCommitSignoffRequired { get; private set; } + public IReadOnlyDictionary CustomProperties { get; private set; } + internal string DebuggerDisplay { get