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