diff --git a/Octokit.Reactive/Clients/IObservableOrganizationHooksClient.cs b/Octokit.Reactive/Clients/IObservableOrganizationHooksClient.cs new file mode 100644 index 0000000000..533a0b4e9d --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableOrganizationHooksClient.cs @@ -0,0 +1,65 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; + +namespace Octokit.Reactive +{ + public interface IObservableOrganizationHooksClient + { + /// + /// Gets the list of hooks defined for a organization + /// + /// The organizations name + /// See API documentation for more information. + IObservable GetAll(string org); + + /// + /// Gets the list of hooks defined for a organization + /// + /// The organizations name + /// Options for changing the API response + /// See API documentation for more information. + IObservable GetAll(string org, ApiOptions options); + + /// + /// Gets a single hook defined for a organization by id + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get", Justification = "This is ok; we're matching HTTP verbs not keyworks")] + IObservable Get(string org, int hookId); + + /// + /// Creates a hook for a organization + /// + /// See API documentation for more information. + /// + IObservable Create(string org, NewOrganizationHook hook); + + /// + /// Edits a hook for a organization + /// + /// The organizations name + /// The organizations hook id + /// The hook's parameters + /// See API documentation for more information. + IObservable Edit(string org, int hookId, EditOrganizationHook hook); + + /// + /// This will trigger a ping event to be sent to the hook. + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + IObservable Ping(string org, int hookId); + + /// + /// Deletes a hook for a organization + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + IObservable Delete(string org, int hookId); + } +} \ No newline at end of file diff --git a/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs b/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs index cda5a4727d..793b17846e 100644 --- a/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs +++ b/Octokit.Reactive/Clients/IObservableOrganizationsClient.cs @@ -15,6 +15,12 @@ public interface IObservableOrganizationsClient /// IObservableTeamsClient Team { get; } + /// + /// A client for GitHub's Organization Hooks API. + /// + /// See Hooks API documentation for more information. + IObservableOrganizationHooksClient Hook { get; } + /// /// Returns a client to manage outside collaborators of an organization. /// @@ -77,10 +83,10 @@ public interface IObservableOrganizationsClient /// /// Update the specified organization with data from . /// - /// The name of the organization to update. + /// The name of the organization to update. /// /// Thrown if the client is not authenticated. /// A - IObservable Update(string organizationName, OrganizationUpdate updateRequest); + IObservable Update(string org, OrganizationUpdate updateRequest); } } diff --git a/Octokit.Reactive/Clients/ObservableOrganizationHooksClient.cs b/Octokit.Reactive/Clients/ObservableOrganizationHooksClient.cs new file mode 100644 index 0000000000..b8e78925b6 --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableOrganizationHooksClient.cs @@ -0,0 +1,116 @@ +using System; +using System.Reactive; +using System.Reactive.Threading.Tasks; +using Octokit.Reactive.Internal; + +namespace Octokit.Reactive +{ + public class ObservableOrganizationHooksClient : IObservableOrganizationHooksClient + { + readonly IOrganizationHooksClient _client; + readonly IConnection _connection; + + public ObservableOrganizationHooksClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, nameof(client)); + + _client = client.Organization.Hook; + _connection = client.Connection; + } + + /// + /// Gets the list of hooks defined for a organization + /// + /// The organizations name + /// See API documentation for more information. + public IObservable GetAll(string org) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return _connection.GetAndFlattenAllPages(ApiUrls.OrganizationHooks(org)); + } + + /// + /// Gets the list of hooks defined for a organization + /// + /// The organizations name + /// Options for changing the API response + /// See API documentation for more information. + public IObservable GetAll(string org, ApiOptions options) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(options, nameof(options)); + + return _connection.GetAndFlattenAllPages(ApiUrls.OrganizationHooks(org), options); + } + + /// + /// Gets a single hook defined for a organization by id + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + public IObservable Get(string org, int hookId) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return _client.Get(org, hookId).ToObservable(); + } + + /// + /// Creates a hook for a organization + /// + /// The organizations name + /// The hook's parameters + /// See API documentation for more information. + public IObservable Create(string org, NewOrganizationHook hook) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(hook, nameof(hook)); + + return _client.Create(org, hook).ToObservable(); + } + + /// + /// Edits a hook for a organization + /// + /// The organizations name + /// The organizations hook id + /// The hook's parameters + /// See API documentation for more information. + /// + public IObservable Edit(string org, int hookId, EditOrganizationHook hook) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(hook, nameof(hook)); + + return _client.Edit(org, hookId, hook).ToObservable(); + } + + /// + /// This will trigger a ping event to be sent to the hook. + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + public IObservable Ping(string org, int hookId) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return _client.Ping(org, hookId).ToObservable(); + } + + /// + /// Deletes a hook for a organization + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + public IObservable Delete(string org, int hookId) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return _client.Delete(org, hookId).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs b/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs index 690375eb2b..ec7e43f40f 100644 --- a/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs +++ b/Octokit.Reactive/Clients/ObservableOrganizationsClient.cs @@ -19,6 +19,7 @@ public ObservableOrganizationsClient(IGitHubClient client) Member = new ObservableOrganizationMembersClient(client); Team = new ObservableTeamsClient(client); + Hook = new ObservableOrganizationHooksClient(client); OutsideCollaborator = new ObservableOrganizationOutsideCollaboratorsClient(client); _client = client.Organization; @@ -35,6 +36,12 @@ public ObservableOrganizationsClient(IGitHubClient client) /// public IObservableTeamsClient Team { get; private set; } + /// + /// A client for GitHub's Organization Hooks API. + /// + /// See Hooks API documentation for more information. + public IObservableOrganizationHooksClient Hook { get; private set; } + /// /// Returns a client to manage outside collaborators of an organization. /// @@ -125,16 +132,17 @@ public IObservable GetAll(OrganizationRequest request) /// /// Update the specified organization with data from . /// - /// The name of the organization to update. + /// The name of the organization to update. /// /// Thrown if the client is not authenticated. /// A - public IObservable Update(string organizationName, OrganizationUpdate updateRequest) + public IObservable Update(string org, OrganizationUpdate updateRequest) { - Ensure.ArgumentNotNullOrEmptyString(organizationName, nameof(organizationName)); + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); Ensure.ArgumentNotNull(updateRequest, nameof(updateRequest)); - return _client.Update(organizationName, updateRequest).ToObservable(); + return _client.Update(org, updateRequest).ToObservable(); } + } } diff --git a/Octokit.Tests.Integration/Clients/OrganizationHooksClientTests.cs b/Octokit.Tests.Integration/Clients/OrganizationHooksClientTests.cs new file mode 100644 index 0000000000..a70b089934 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/OrganizationHooksClientTests.cs @@ -0,0 +1,255 @@ +using Octokit.Tests.Integration.Fixtures; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Integration.Clients +{ + public class OrganizationHooksClientTests + { + [Collection(OrganizationsHooksCollection.Name)] + public class TheGetAllMethod + { + readonly OrganizationsHooksFixture _fixture; + + public TheGetAllMethod(OrganizationsHooksFixture fixture) + { + _fixture = fixture; + } + + [IntegrationTest] + public async Task ReturnsAllHooksFromOrganization() + { + var github = Helper.GetAuthenticatedClient(); + + var hooks = await github.Organization.Hook.GetAll( _fixture.org); + + Assert.Equal(_fixture.ExpectedHooks.Count, hooks.Count); + + var actualHook = hooks[0]; + AssertHook(_fixture.ExpectedHook, actualHook); + } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfHooksWithoutStart() + { + var github = Helper.GetAuthenticatedClient(); + + var options = new ApiOptions + { + PageSize = 5, + PageCount = 1 + }; + + var hooks = await github.Organization.Hook.GetAll(_fixture.org, options); + + Assert.Equal(_fixture.ExpectedHooks.Count, hooks.Count); + } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfHooksWithStart() + { + var github = Helper.GetAuthenticatedClient(); + + var options = new ApiOptions + { + PageSize = 3, + PageCount = 1, + StartPage = 2 + }; + + var hooks = await github.Organization.Hook.GetAll(_fixture.org, options); + + Assert.Equal(1, hooks.Count); + } + + [IntegrationTest] + public async Task ReturnsDistinctResultsBasedOnStartPage() + { + var github = Helper.GetAuthenticatedClient(); + + var startOptions = new ApiOptions + { + PageSize = 2, + PageCount = 1 + }; + + var firstPage = await github.Organization.Hook.GetAll(_fixture.org, startOptions); + + var skipStartOptions = new ApiOptions + { + PageSize = 2, + PageCount = 1, + StartPage = 2 + }; + + var secondPage = await github.Organization.Hook.GetAll(_fixture.org, skipStartOptions); + + Assert.NotEqual(firstPage[0].Id, secondPage[0].Id); + Assert.NotEqual(firstPage[1].Id, secondPage[1].Id); + } + } + + [Collection(OrganizationsHooksCollection.Name)] + public class TheGetMethod + { + readonly OrganizationsHooksFixture _fixture; + + public TheGetMethod(OrganizationsHooksFixture fixture) + { + _fixture = fixture; + } + + [IntegrationTest] + public async Task GetHookByCreatedId() + { + var github = Helper.GetAuthenticatedClient(); + + var actualHook = await github.Organization.Hook.Get(_fixture.org, _fixture.ExpectedHook.Id); + + AssertHook(_fixture.ExpectedHook, actualHook); + } + } + + [Collection(OrganizationsHooksCollection.Name)] + public class TheCreateMethod + { + readonly OrganizationsHooksFixture _fixture; + + public TheCreateMethod(OrganizationsHooksFixture fixture) + { + _fixture = fixture; + } + [IntegrationTest] + public async Task CreateAWebHookForTestOrganization() + { + var github = Helper.GetAuthenticatedClient(); + var url = "http://test.com/example"; + var contentType = OrgWebHookContentType.Json; + //var secret = "53cr37"; + var config = new Dictionary + { + { "url", "http://hostname.url" }, + { "content_type", "json" } + }; + var parameters = new NewOrganizationHook("web", config) + { + Events = new[] { "push" }, + Active = false + }; + + var hook = await github.Organization.Hook.Create(_fixture.org, parameters.ToRequest()); + + var baseHookUrl = CreateExpectedBaseHookUrl(_fixture.org, hook.Id); + var webHookConfig = CreateExpectedConfigDictionary(config, url, contentType); + + Assert.Equal("web", hook.Name); + Assert.Equal(new[] { "push" }.ToList(), hook.Events.ToList()); + Assert.Equal(baseHookUrl, hook.Url); + Assert.Equal(baseHookUrl + "/pings", hook.PingUrl); + Assert.NotNull(hook.CreatedAt); + Assert.NotNull(hook.UpdatedAt); + Assert.Equal(webHookConfig.Keys, hook.Config.Keys); + //Assert.Equal(webHookConfig.Values, hook.Config.Values); + Assert.Equal(false, hook.Active); + } + + Dictionary CreateExpectedConfigDictionary(Dictionary config, string url, OrgWebHookContentType contentType) + { + return new Dictionary + { + + }.Union(config).ToDictionary(k => k.Key, v => v.Value); + } + + string CreateExpectedBaseHookUrl(string org, int id) + { + return "https://api.github.com/orgs/" + org+ "/hooks/" + id; + } + } + + [Collection(OrganizationsHooksCollection.Name)] + public class TheEditMethod + { + readonly OrganizationsHooksFixture _fixture; + + public TheEditMethod(OrganizationsHooksFixture fixture) + { + _fixture = fixture; + } + + [IntegrationTest] + public async Task EditHookTest() + { + var github = Helper.GetAuthenticatedClient(); + + var editOrganizationHook = new EditOrganizationHook + { + Events = new[] { "pull_request" } + }; + + var actualHook = await github.Organization.Hook.Edit( _fixture.org, _fixture.ExpectedHook.Id, editOrganizationHook); + + var expectedConfig = new Dictionary { { "content_type", "json" }, { "url", "http://test.com/example" } }; + Assert.Equal(new[] { "commit_comment", "pull_request" }.ToList(), actualHook.Events.ToList()); + Assert.Equal(expectedConfig.Keys, actualHook.Config.Keys); + Assert.Equal(expectedConfig.Values, actualHook.Config.Values); + } + } + + [Collection(OrganizationsHooksCollection.Name)] + public class ThePingMethod + { + readonly OrganizationsHooksFixture _fixture; + + public ThePingMethod(OrganizationsHooksFixture fixture) + { + _fixture = fixture; + } + + [IntegrationTest] + public async Task PingACreatedHook() + { + var github = Helper.GetAuthenticatedClient(); + + await github.Organization.Hook.Ping( _fixture.org, _fixture.ExpectedHook.Id); + } + } + + [Collection(OrganizationsHooksCollection.Name)] + public class TheDeleteMethod + { + readonly OrganizationsHooksFixture _fixture; + + public TheDeleteMethod(OrganizationsHooksFixture fixture) + { + _fixture = fixture; + } + + [IntegrationTest] + public async Task DeleteCreatedWebHook() + { + var github = Helper.GetAuthenticatedClient(); + + await github.Organization.Hook.Delete(_fixture.org, _fixture.ExpectedHook.Id); + var hooks = await github.Organization.Hook.GetAll( _fixture.org); + + Assert.Empty(hooks); + } + } + + static void AssertHook(OrganizationHook expectedHook, OrganizationHook actualHook) + { + Assert.Equal(expectedHook.Id, actualHook.Id); + Assert.Equal(expectedHook.Active, actualHook.Active); + Assert.Equal(expectedHook.Config, actualHook.Config); + Assert.Equal(expectedHook.CreatedAt, actualHook.CreatedAt); + Assert.Equal(expectedHook.Name, actualHook.Name); + Assert.Equal(expectedHook.PingUrl, actualHook.PingUrl); + Assert.Equal(expectedHook.TestUrl, actualHook.TestUrl); + Assert.Equal(expectedHook.UpdatedAt, actualHook.UpdatedAt); + Assert.Equal(expectedHook.Url, actualHook.Url); + } + } +} diff --git a/Octokit.Tests.Integration/Clients/RepositoryHooksClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryHooksClientTests.cs index 2556de915b..838d959a92 100644 --- a/Octokit.Tests.Integration/Clients/RepositoryHooksClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoryHooksClientTests.cs @@ -1,4 +1,4 @@ -using Octokit.Tests.Integration.fixtures; +using Octokit.Tests.Integration.Fixtures; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/Octokit.Tests.Integration/Reactive/ObservableRepositoryHooksClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableRepositoryHooksClientTests.cs index 4998af8c58..e863226e22 100644 --- a/Octokit.Tests.Integration/Reactive/ObservableRepositoryHooksClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/ObservableRepositoryHooksClientTests.cs @@ -1,8 +1,8 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Octokit.Reactive; -using Octokit.Tests.Integration.fixtures; using Xunit; +using Octokit.Tests.Integration.Fixtures; namespace Octokit.Tests.Integration.Reactive { diff --git a/Octokit.Tests.Integration/fixtures/OrganizationsHooksCollection.cs b/Octokit.Tests.Integration/fixtures/OrganizationsHooksCollection.cs new file mode 100644 index 0000000000..a247d7ffbe --- /dev/null +++ b/Octokit.Tests.Integration/fixtures/OrganizationsHooksCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Octokit.Tests.Integration.Fixtures +{ + [CollectionDefinition(Name)] + public class OrganizationsHooksCollection : ICollectionFixture + { + public const string Name = "Organization Hooks Collection"; + } +} diff --git a/Octokit.Tests.Integration/fixtures/OrganizationsHooksFixture.cs b/Octokit.Tests.Integration/fixtures/OrganizationsHooksFixture.cs new file mode 100644 index 0000000000..d708b508f7 --- /dev/null +++ b/Octokit.Tests.Integration/fixtures/OrganizationsHooksFixture.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace Octokit.Tests.Integration.Fixtures +{ + public class OrganizationsHooksFixture : IDisposable + { + readonly IGitHubClient _github; + readonly OrganizationHook _hook; + readonly private string _organizationFixture; + readonly IList _hooks; + + public OrganizationsHooksFixture() + { + _github = Helper.GetAuthenticatedClient(); + _organizationFixture = Helper.Organization; + _hooks = new List(5) + { + CreateHook(_github, _organizationFixture, "awscodedeploy", "deployment"), + CreateHook(_github, _organizationFixture, "awsopsworks", "push"), + CreateHook(_github, _organizationFixture, "activecollab", "push"), + CreateHook(_github, _organizationFixture, "acunote", "push") + }; + _hook = _hooks[0]; + } + + public string org { get { return _organizationFixture; } } + + public OrganizationHook ExpectedHook { get { return _hook; } } + + public IList ExpectedHooks { get { return _hooks; } } + + public void Dispose() + { + _github.Organization.Hook.Delete(_organizationFixture,_hook.Id); + } + + static OrganizationHook CreateHook(IGitHubClient github, string orgFixture, string hookName, string eventName) + { + var config = new Dictionary { { "content_type", "json" }, { "url", "http://test.com/example" } }; + var parameters = new NewOrganizationHook(hookName, config) + { + Events = new[] { eventName }, + Active = false + }; + var createdHook = github.Organization.Hook.Create(orgFixture, parameters); + + return createdHook.Result; + } + } +} diff --git a/Octokit.Tests.Integration/fixtures/RepositoriesHooksCollection.cs b/Octokit.Tests.Integration/fixtures/RepositoriesHooksCollection.cs index 0d4006f5e7..cff098334b 100644 --- a/Octokit.Tests.Integration/fixtures/RepositoriesHooksCollection.cs +++ b/Octokit.Tests.Integration/fixtures/RepositoriesHooksCollection.cs @@ -1,6 +1,6 @@ using Xunit; -namespace Octokit.Tests.Integration.fixtures +namespace Octokit.Tests.Integration.Fixtures { [CollectionDefinition(Name)] public class RepositoriesHooksCollection : ICollectionFixture diff --git a/Octokit.Tests.Integration/fixtures/RepositoriesHooksFixture.cs b/Octokit.Tests.Integration/fixtures/RepositoriesHooksFixture.cs index 2ce734a8f4..76692d6f82 100644 --- a/Octokit.Tests.Integration/fixtures/RepositoriesHooksFixture.cs +++ b/Octokit.Tests.Integration/fixtures/RepositoriesHooksFixture.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Octokit.Tests.Integration.fixtures +namespace Octokit.Tests.Integration.Fixtures { public class RepositoriesHooksFixture : IDisposable { diff --git a/Octokit.Tests/Clients/OrganizationHooksClientTest.cs b/Octokit.Tests/Clients/OrganizationHooksClientTest.cs new file mode 100644 index 0000000000..7a06b95d9b --- /dev/null +++ b/Octokit.Tests/Clients/OrganizationHooksClientTest.cs @@ -0,0 +1,248 @@ +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class OrganizationHooksClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws( + () => new OrganizationHooksClient(null)); + } + } + + public class TheGetAllMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + + await client.Hook.GetAll("org"); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "orgs/org/hooks")); + } + + [Fact] + public async Task RequestsCorrectUrlWithApiOptions() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + + var options = new ApiOptions + { + PageCount = 1, + PageSize = 1, + StartPage = 1 + }; + + await client.Hook.GetAll("org", options); + + connection.Received(1) + .GetAll(Arg.Is(u => u.ToString() == "orgs/org/hooks"), + options); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Hook.GetAll(null)); + } + + [Fact] + public async Task EnsuresNonEmptyArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Hook.GetAll("")); + } + } + + public class TheGetMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + + await client.Hook.Get("org", 12345678); + + connection.Received().Get(Arg.Is(u => u.ToString() == "orgs/org/hooks/12345678")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Hook.Get(null, 123)); + } + + [Fact] + public async Task EnsuresNonEmptyArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Hook.Get("",123)); + } + } + + public class TheCreateMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + var hook = new NewOrganizationHook("name", new Dictionary { { "config", "" } }); + + client.Hook.Create("org", hook); + + connection.Received().Post(Arg.Is(u => u.ToString() == "orgs/org/hooks"), hook); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + var config = new Dictionary { { "config", "" } }; + await Assert.ThrowsAsync(() => client.Hook.Create(null, new NewOrganizationHook("name", config))); + await Assert.ThrowsAsync(() => client.Hook.Create("name", null)); + } + + [Fact] + public void EnsuresNonEmptyArguments() + { + var client = new OrganizationsClient(Substitute.For()); + var config = new Dictionary { { "url", "" } }; + Assert.ThrowsAsync(() => client.Hook.Create("", new NewOrganizationHook("name", config))); + } + + [Fact] + public void UsesTheSuppliedHook() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + var newOrganizationHook = new NewOrganizationHook("name", new Dictionary { { "config", "" } }); + + client.Hook.Create("org", newOrganizationHook); + + connection.Received().Post(Arg.Any(), newOrganizationHook); + } + } + + public class TheEditMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + var hook = new EditOrganizationHook(); + + client.Hook.Edit("org", 12345678, hook); + + connection.Received().Patch(Arg.Is(u => u.ToString() == "orgs/org/hooks/12345678"), hook); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Hook.Edit( null, 12345678, new EditOrganizationHook())); + await Assert.ThrowsAsync(() => client.Hook.Edit( "name", 12345678, null)); + } + + [Fact] + public void EnsuresNonEmptyArguments() + { + var client = new OrganizationsClient(Substitute.For()); + Assert.ThrowsAsync(() => client.Hook.Edit("", 123, new EditOrganizationHook())); + } + + [Fact] + public void UsesTheSuppliedHook() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + var editOrganizationHook = new EditOrganizationHook() { Active = false }; + + client.Hook.Edit("org", 12345678, editOrganizationHook); + + connection.Received().Patch(Arg.Any(), editOrganizationHook); + } + } + + public class ThePingMethod + { + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Hook.Ping(null, 12345678)); + } + + [Fact] + public void EnsuresNonEmptyArguments() + { + var client = new OrganizationsClient(Substitute.For()); + Assert.ThrowsAsync(() => client.Hook.Ping("", 123)); + } + + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + + client.Hook.Ping("org", 12345678); + + connection.Received().Post(Arg.Is(u => u.ToString() == "orgs/org/hooks/12345678/pings")); + } + } + + public class TheDeleteMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new OrganizationsClient(connection); + + client.Hook.Delete("org", 12345678); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "orgs/org/hooks/12345678")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new OrganizationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Hook.Delete(null, 12345678)); + } + + [Fact] + public void EnsuresNonEmptyArguments() + { + var client = new OrganizationsClient(Substitute.For()); + Assert.ThrowsAsync(() => client.Hook.Delete("", 123)); + } + + } + } +} diff --git a/Octokit.Tests/Models/NewOrganizationWebHookTests.cs b/Octokit.Tests/Models/NewOrganizationWebHookTests.cs new file mode 100644 index 0000000000..ae38c6709b --- /dev/null +++ b/Octokit.Tests/Models/NewOrganizationWebHookTests.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class NewOrganizationWebHookTests + { + public class TheCtor + { + [Fact] + public void UsesDefaultValuesForDefaultConfig() + { + var create = new NewOrganizationWebHook("windowsazure", new Dictionary(), "http://test.com/example"); + Assert.Equal(create.Url, "http://test.com/example"); + Assert.Equal(create.ContentType, OrgWebHookContentType.Form); + Assert.Empty(create.Secret); + Assert.False(create.InsecureSsl); + + var request = create.ToRequest(); + Assert.Equal(request.Config.Count, 4); + + Assert.True(request.Config.ContainsKey("url")); + Assert.True(request.Config.ContainsKey("content_type")); + Assert.True(request.Config.ContainsKey("secret")); + Assert.True(request.Config.ContainsKey("insecure_ssl")); + + Assert.Equal(request.Config["url"], "http://test.com/example"); + Assert.Equal(request.Config["content_type"], OrgWebHookContentType.Form.ToParameter()); + Assert.Equal(request.Config["secret"], ""); + Assert.Equal(request.Config["insecure_ssl"], "False"); + } + + [Fact] + public void CombinesUserSpecifiedContentTypeWithConfig() + { + var config = new Dictionary + { + {"hostname", "http://hostname.url"}, + {"username", "username"}, + {"password", "password"} + }; + + var create = new NewOrganizationWebHook("windowsazure", config, "http://test.com/example") + { + ContentType = OrgWebHookContentType.Json, + Secret = string.Empty, + InsecureSsl = true + }; + + Assert.Equal(create.Url, "http://test.com/example"); + Assert.Equal(create.ContentType, OrgWebHookContentType.Json); + Assert.Empty(create.Secret); + Assert.True(create.InsecureSsl); + + var request = create.ToRequest(); + + Assert.Equal(request.Config.Count, 7); + + Assert.True(request.Config.ContainsKey("url")); + Assert.True(request.Config.ContainsKey("content_type")); + Assert.True(request.Config.ContainsKey("secret")); + Assert.True(request.Config.ContainsKey("insecure_ssl")); + + Assert.Equal(request.Config["url"], "http://test.com/example"); + Assert.Equal(request.Config["content_type"], OrgWebHookContentType.Json.ToParameter()); + Assert.Equal(request.Config["secret"], ""); + Assert.Equal(request.Config["insecure_ssl"], true.ToString()); + + Assert.True(request.Config.ContainsKey("hostname")); + Assert.Equal(request.Config["hostname"], config["hostname"]); + Assert.True(request.Config.ContainsKey("username")); + Assert.Equal(request.Config["username"], config["username"]); + Assert.True(request.Config.ContainsKey("password")); + Assert.Equal(request.Config["password"], config["password"]); + } + + [Fact] + public void CanSetHookEvents() + { + var create = new NewOrganizationWebHook("web", new Dictionary(), "http://test.com/example") + { + Events = new List { "*" } + }; + + var request = create.ToRequest(); + + Assert.Contains("*", request.Events); + } + + [Fact] + public void EnsureCanCallToRequestMultipleTimes() + { + var create = new NewOrganizationWebHook("web", new Dictionary(), "http://test.com/example") + { + Events = new List { "*" } + }; + + var request = create.ToRequest(); + var requestRepeated = create.ToRequest(); + + Assert.Contains("*", request.Events); + Assert.Contains("*", requestRepeated.Events); + } + + [Fact] + public void ShouldNotContainDuplicateConfigEntriesOnSubsequentRequests() + { + var create = new NewOrganizationWebHook("web", new Dictionary(), "http://test.com/example"); + + var request = create.ToRequest(); + var requestRepeated = create.ToRequest(); + + Assert.Equal(request.Config.Count, 4); + Assert.Equal(requestRepeated.Config.Count, 4); + } + + [Fact] + public void ShouldNotContainDuplicateConfigEntriesOnSubsequentRequestsWithCustomisedConfig() + { + var config = new Dictionary + { + {"url", "http://example.com/test"}, + {"hostname", "http://hostname.url"}, + {"username", "username"}, + {"password", "password"} + }; + + var create = new NewOrganizationWebHook("web", config, "http://test.com/example"); + + var request = create.ToRequest(); + var requestRepeated = create.ToRequest(); + + //This is not 8, because `url` used in config, is already part of the base config + Assert.Equal(request.Config.Count, 7); + Assert.Equal(requestRepeated.Config.Count, 7); + } + + [Fact] + public void PropertiesShouldTakePrecedenceOverConfigPassedIn() + { + var config = new Dictionary + { + {"url", "http://originalurl.com/test"}, + }; + + var create = new NewOrganizationWebHook("web", config, "http://test.com/example"); + + var request = create.ToRequest(); + + Assert.Equal(request.Config["url"], "http://test.com/example"); + + var subsequentRequest = create.ToRequest(); + Assert.Equal(subsequentRequest.Config["url"], "http://test.com/example"); + } + } + } +} diff --git a/Octokit.Tests/Reactive/ObservableOrganizationHooksClientTests.cs b/Octokit.Tests/Reactive/ObservableOrganizationHooksClientTests.cs new file mode 100644 index 0000000000..ed8fe73107 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservableOrganizationHooksClientTests.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using NSubstitute; +using Octokit.Reactive; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservableOrganizationHooksClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws( + () => new ObservableOrganizationHooksClient(null)); + } + } + + public class TheGetAllMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationHooksClient(gitHubClient); + + client.GetAll("org"); + + gitHubClient.Received().Organization.Hook.GetAll("org"); + } + + [Fact] + public void RequestsCorrectUrlWithApiOptions() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationHooksClient(gitHubClient); + + var options = new ApiOptions + { + PageCount = 1, + PageSize = 1, + StartPage = 1 + }; + + client.GetAll("org", options); + + gitHubClient.Received(1).Organization.Hook.GetAll("org", options); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableOrganizationHooksClient(Substitute.For()); + + Assert.Throws(() => client.GetAll(null, ApiOptions.None)); + Assert.Throws(() => client.GetAll("org", null)); + Assert.Throws(() => client.GetAll("")); + Assert.Throws(() => client.GetAll("", null)); + } + } + + public class TheGetMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationHooksClient(gitHubClient); + + client.Get("org", 12345678); + + gitHubClient.Received().Organization.Hook.Get("org", 12345678); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableOrganizationHooksClient(Substitute.For()); + + Assert.Throws(() => client.Get(null, 123)); + Assert.Throws(() => client.Get("", 123)); + } + } + + public class TheCreateMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationHooksClient(gitHubClient); + + var hook = new NewOrganizationHook("name", new Dictionary { { "config", "" } }); + + client.Create("org", hook); + + gitHubClient.Received().Organization.Hook.Create("org", hook); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableOrganizationHooksClient(Substitute.For()); + + var config = new Dictionary { { "config", "" } }; + + Assert.Throws(() => client.Create(null, new NewOrganizationHook("name", config))); + Assert.Throws(() => client.Create("org", null)); + Assert.Throws(() => client.Create("", new NewOrganizationHook("name", config))); + } + } + + public class TheEditMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationHooksClient(gitHubClient); + + var hook = new EditOrganizationHook(); + + client.Edit("org", 12345678, hook); + + gitHubClient.Received().Organization.Hook.Edit("org", 12345678, hook); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableOrganizationHooksClient(Substitute.For()); + + Assert.Throws(() => client.Edit(null, 12345678, new EditOrganizationHook())); + Assert.Throws(() => client.Edit("org", 12345678, null)); + Assert.Throws(() => client.Edit("", 12345678, new EditOrganizationHook())); + } + } + + public class ThePingMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationHooksClient(gitHubClient); + + client.Ping("org", 12345678); + + gitHubClient.Received().Organization.Hook.Ping("org", 12345678); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableOrganizationHooksClient(Substitute.For()); + + Assert.Throws(() => client.Ping(null, 12345678)); + Assert.Throws(() => client.Ping("", 12345678)); + } + } + + public class TheDeleteMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableOrganizationHooksClient(gitHubClient); + + client.Delete("org", 12345678); + + gitHubClient.Received().Organization.Hook.Delete("org", 12345678); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableOrganizationHooksClient(Substitute.For()); + + Assert.Throws(() => client.Delete(null, 12345678)); + Assert.Throws(() => client.Delete("", 12345678)); + } + } + } +} diff --git a/Octokit/Clients/IOrganizationHooksClient.cs b/Octokit/Clients/IOrganizationHooksClient.cs new file mode 100644 index 0000000000..3d8820a0fc --- /dev/null +++ b/Octokit/Clients/IOrganizationHooksClient.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Organization Webhooks API. + /// + /// + /// See the Webhooks API documentation for more information. + /// + public interface IOrganizationHooksClient + { + /// + /// Gets the list of hooks defined for a organization + /// + /// The organizations name + /// See API documentation for more information. + Task> GetAll(string org); + + /// + /// Gets the list of hooks defined for a organization + /// + /// The organizations name + /// Options for changing the API response + /// See API documentation for more information. + Task> GetAll(string org, ApiOptions options); + + /// + /// Gets a single hook by Id + /// + /// The organizations name + /// The repository's hook id + /// See API documentation for more information. + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get", Justification = "This is ok; we're matching HTTP verbs not keywords")] + Task Get(string org, int hookId); + + /// + /// Creates a hook for a organization + /// + /// The organizations name + /// The hook's parameters + /// See API documentation for more information. + Task Create(string org, NewOrganizationHook hook); + + /// + /// Edits a hook for a organization + /// + /// The organizations name + /// The organizations hook id + /// The hook's parameters + /// See API documentation for more information. + Task Edit(string org, int hookId, EditOrganizationHook hook); + + /// + /// This will trigger a ping event to be sent to the hook. + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + Task Ping(string org, int hookId); + + /// + /// Deletes a hook for a organization + /// + /// The organizations name + /// The organizations hook id + /// See API documentation for more information. + Task Delete(string org, int hookId); + } +} diff --git a/Octokit/Clients/IOrganizationsClient.cs b/Octokit/Clients/IOrganizationsClient.cs index 2d915cc42d..faaf082f3d 100644 --- a/Octokit/Clients/IOrganizationsClient.cs +++ b/Octokit/Clients/IOrganizationsClient.cs @@ -24,6 +24,12 @@ public interface IOrganizationsClient /// ITeamsClient Team { get; } + /// + /// A client for GitHub's Organization Hooks API. + /// + /// See Hooks API documentation for more information. + IOrganizationHooksClient Hook { get; } + /// /// Returns a client to manage outside collaborators of an organization. /// @@ -94,10 +100,10 @@ public interface IOrganizationsClient /// /// Update the specified organization with data from . /// - /// The name of the organization to update. + /// The name of the organization to update. /// /// Thrown if the client is not authenticated. /// A - Task Update(string organizationName, OrganizationUpdate updateRequest); + Task Update(string org, OrganizationUpdate updateRequest); } } diff --git a/Octokit/Clients/OrganizationHooksClient.cs b/Octokit/Clients/OrganizationHooksClient.cs new file mode 100644 index 0000000000..a7bc46ffc1 --- /dev/null +++ b/Octokit/Clients/OrganizationHooksClient.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + public class OrganizationHooksClient : ApiClient, IOrganizationHooksClient + { + /// + /// Initializes a new GitHub Repos API client. + /// + /// An API connection. + public OrganizationHooksClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// Gets the list of hooks defined for a organization + /// + /// The organization's name + /// See API documentation for more information. + /// + [ManualRoute("GET", "orgs/{org}/hooks")] + public Task> GetAll(string org) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + + return ApiConnection.GetAll(ApiUrls.OrganizationHooks(org)); + } + + /// + /// Gets the list of hooks defined for a organization + /// + /// The organization's name + /// Options for changing the API response + /// See API documentation for more information. + /// + [ManualRoute("GET", "orgs/{org}/hooks")] + public Task> GetAll(string org, ApiOptions options) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(options, nameof(options)); + + return ApiConnection.GetAll(ApiUrls.OrganizationHooks(org), options); + } + + /// + /// Gets a single hook by Id + /// + /// + /// + /// + /// See API documentation for more information. + [ManualRoute("GET", "orgs/{org}/hooks/{hook_id}")] + public Task Get(string org, int hookId) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(hookId, nameof(hookId)); + + return ApiConnection.Get(ApiUrls.OrganizationHookById(org, hookId)); + } + + /// + /// Creates a hook for a organization + /// + /// See API documentation for more information. + /// + [ManualRoute("POST", "orgs/{org}/hooks")] + public Task Create(string org, NewOrganizationHook hook) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(hook, nameof(hook)); + + return ApiConnection.Post(ApiUrls.OrganizationHooks(org), hook.ToRequest()); + } + + /// + /// Edits a hook for a organization + /// + /// See API documentation for more information. + /// + [ManualRoute("PATCH", "orgs/{org}/hooks/{hook_id}")] + public Task Edit(string org, int hookId, EditOrganizationHook hook) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(hook, nameof(hook)); + + return ApiConnection.Patch(ApiUrls.OrganizationHookById(org, hookId), hook); + } + + /// + /// This will trigger a ping event to be sent to the hook. + /// + /// See API documentation for more information. + /// + [ManualRoute("POST", "orgs/{org}/hooks/{hook_id}/pings")] + public Task Ping(string org, int hookId) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(hookId, nameof(hookId)); + return ApiConnection.Post(ApiUrls.OrganizationHookPing(org, hookId)); + } + + /// + /// Deletes a hook for a organization + /// + /// See API documentation for more information. + /// + [ManualRoute("DELETE", "orgs/{org}/hooks/{hook_id}")] + public Task Delete(string org, int hookId) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNull(hookId, nameof(hookId)); + + return ApiConnection.Delete(ApiUrls.OrganizationHookById(org, hookId)); + } + } +} diff --git a/Octokit/Clients/OrganizationsClient.cs b/Octokit/Clients/OrganizationsClient.cs index b9f4a44b39..5d13ca1895 100644 --- a/Octokit/Clients/OrganizationsClient.cs +++ b/Octokit/Clients/OrganizationsClient.cs @@ -20,6 +20,7 @@ public OrganizationsClient(IApiConnection apiConnection) : base(apiConnection) { Member = new OrganizationMembersClient(apiConnection); Team = new TeamsClient(apiConnection); + Hook = new OrganizationHooksClient(apiConnection); OutsideCollaborator = new OrganizationOutsideCollaboratorsClient(apiConnection); } @@ -52,6 +53,12 @@ public Task Get(string org) return ApiConnection.Get(ApiUrls.Organization(org)); } + /// + /// A client for GitHub's Organization Hooks API. + /// + /// See Hooks API documentation for more information. + public IOrganizationHooksClient Hook { get; private set; } + /// /// Returns all s for the current user. /// @@ -138,17 +145,17 @@ public Task> GetAll(OrganizationRequest request) /// /// Update the specified organization with data from . /// - /// The name of the organization to update. + /// The name of the organization to update. /// /// Thrown if the client is not authenticated. /// A [ManualRoute("PATCH", "/orgs/{org}")] - public Task Update(string organizationName, OrganizationUpdate updateRequest) + public Task Update(string org, OrganizationUpdate updateRequest) { - Ensure.ArgumentNotNullOrEmptyString(organizationName, nameof(organizationName)); + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); Ensure.ArgumentNotNull(updateRequest, nameof(updateRequest)); - var updateUri = new Uri("orgs/" + organizationName, UriKind.Relative); + var updateUri = new Uri("orgs/" + org, UriKind.Relative); return ApiConnection.Patch(updateUri, updateRequest); } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 9b95f9c192..aa52d18df9 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -985,6 +985,38 @@ public static Uri RepositoryHookPing(string owner, string name, int hookId) return "repos/{0}/{1}/hooks/{2}/pings".FormatUri(owner, name, hookId); } + /// + /// Returns the that lists the organization hooks for the specified reference. + /// + /// The name of the organization + /// + public static Uri OrganizationHooks(string org) + { + return "orgs/{0}/hooks".FormatUri(org); + } + + /// + /// Returns the that gets the organization hook for the specified reference. + /// + /// The name of the organization + /// The identifier of the organization hook + /// + public static Uri OrganizationHookById(string org, int hookId) + { + return "orgs/{0}/hooks/{1}".FormatUri(org, hookId); + } + + /// + /// Returns the that can ping a specified organization hook + /// + /// The name of the organization + /// The identifier of the organization hook + /// + public static Uri OrganizationHookPing(string org, int hookId) + { + return "orgs/{0}/hooks/{1}/pings".FormatUri(org, hookId); + } + /// /// Returns the that lists the commit statuses for the specified reference. /// diff --git a/Octokit/Models/Request/EditOrganizationHook.cs b/Octokit/Models/Request/EditOrganizationHook.cs new file mode 100644 index 0000000000..ecfe146eeb --- /dev/null +++ b/Octokit/Models/Request/EditOrganizationHook.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Represents the requested changes to an edit repository hook. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class EditOrganizationHook + { + /// + /// Initializes a new instance of the class. + /// + public EditOrganizationHook() : this(null) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public EditOrganizationHook(IDictionary config) + { + Config = config; + } + + public IDictionary Config { get; private set; } + + /// + /// Gets or sets the events. + /// + /// + /// The events. + /// + public IEnumerable Events { get; set; } + + /// + /// Gets or sets the active. + /// + /// + /// The active. + /// + public bool? Active { get; set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "Organizaton Hook: Events: {0}", Events == null ? "no" : string.Join(", ", Events)); + } + } + } +} \ No newline at end of file diff --git a/Octokit/Models/Request/NewOrganizationHook.cs b/Octokit/Models/Request/NewOrganizationHook.cs new file mode 100644 index 0000000000..ec2819bb22 --- /dev/null +++ b/Octokit/Models/Request/NewOrganizationHook.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + /// + /// Creates a Webhook for the organization. + /// + /// + /// To create a webhook, the following fields are required by the config: + /// + /// + /// url + /// A required string defining the URL to which the payloads will be delivered. + /// + /// + /// content_type + /// + /// An optional string defining the media type used to serialize the payloads. Supported values include json and + /// form. The default is form. + /// + /// + /// + /// secret + /// + /// An optional string that’s passed with the HTTP requests as an X-Hub-Signature header. The value of this + /// header is computed as the HMAC hex digest of the body, using the secret as the key. + /// + /// + /// + /// insecure_ssl: + /// + /// An optional string that determines whether the SSL certificate of the host for url will be verified when + /// delivering payloads. Supported values include "0" (verification is performed) and "1" (verification is not + /// performed). The default is "0". + /// + /// + /// + /// + /// API: https://developer.github.com/v3/repos/hooks/#create-a-hook + /// + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class NewOrganizationHook + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Use "web" for a webhook or use the name of a valid service. (See + /// https://api.github.com/hooks for the list of valid service + /// names.) + /// + /// + /// Key/value pairs to provide settings for this hook. These settings vary between the services and are + /// defined in the github-services repository. Booleans are stored internally as “1” for true, and “0” for + /// false. Any JSON true/false values will be converted automatically. + /// + public NewOrganizationHook(string name, IReadOnlyDictionary config) + { + Name = name; + Config = config; + } + + /// + /// Gets the name of the hook to create. Use "web" for a webhook or use the name of a valid service. (See + /// https://api.github.com/hooks for the list of valid service + /// names.) + /// + /// + /// The name. + /// + public string Name { get; private set; } + + /// + /// Key/value pairs to provide settings for this hook. These settings vary between the services and are + /// defined in the github-services repository. Booleans are stored internally as “1” for true, and “0” for + /// false. Any JSON true/false values will be converted automatically. + /// + /// + /// The configuration. + /// + public IReadOnlyDictionary Config { get; protected set; } + + /// + /// Determines what events the hook is triggered for. Default: ["push"] + /// + /// + /// The events. + /// + public IEnumerable Events { get; set; } + + /// + /// Determines whether the hook is actually triggered on pushes. + /// + /// + /// true if active; otherwise, false. + /// + public bool Active { get; set; } + + public virtual NewOrganizationHook ToRequest() + { + return this; + } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "Organization Hook: Name: {0}, Events: {1}", Name, string.Join(", ", Events)); + } + } + } +} diff --git a/Octokit/Models/Request/NewOrganizationWebHook.cs b/Octokit/Models/Request/NewOrganizationWebHook.cs new file mode 100644 index 0000000000..8d1dfa1cb5 --- /dev/null +++ b/Octokit/Models/Request/NewOrganizationWebHook.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Octokit +{ + /// + /// Creates a Webhook for the repository. + /// + /// + /// To create a webhook, the following fields are required by the config: + /// + /// + /// url + /// A required string defining the URL to which the payloads will be delivered. + /// + /// + /// content_type + /// + /// An optional string defining the media type used to serialize the payloads. Supported values include json and + /// form. The default is form. + /// + /// + /// + /// secret + /// + /// An optional string that’s passed with the HTTP requests as an X-Hub-Signature header. The value of this + /// header is computed as the HMAC hex digest of the body, using the secret as the key. + /// + /// + /// + /// insecure_ssl: + /// + /// An optional string that determines whether the SSL certificate of the host for url will be verified when + /// delivering payloads. Supported values include "0" (verification is performed) and "1" (verification is not + /// performed). The default is "0". + /// + /// + /// + /// + /// API: https://developer.github.com/v3/repos/hooks/#create-a-hook + /// + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class NewOrganizationWebHook : NewOrganizationHook + { + /// + /// Initializes a new instance of the class. + /// Using default values for ContentType, Secret and InsecureSsl. + /// + /// + /// Use "web" for a webhook or use the name of a valid service. (See + /// https://api.github.com/hooks for the list of valid service + /// names.) + /// + /// + /// Key/value pairs to provide settings for this hook. These settings vary between the services and are + /// defined in the github-services repository. Booleans are stored internally as “1” for true, and “0” for + /// false. Any true/false values will be converted automatically. + /// + /// + /// A required string defining the URL to which the payloads will be delivered. + /// + public NewOrganizationWebHook(string name, IReadOnlyDictionary config, string url) + : base(name, config) + { + Ensure.ArgumentNotNullOrEmptyString(url, "url"); + + Url = url; + ContentType = OrgWebHookContentType.Form; + Secret = ""; + InsecureSsl = false; + } + + /// + /// Gets the URL of the hook to create. + /// + /// + /// The URL. + /// + public string Url { get; protected set; } + + /// + /// Gets the content type used to serialize the payload. The default is `form`. + /// + /// + /// The content type. + /// + public OrgWebHookContentType ContentType { get; set; } + + /// + /// Gets the secret used as the key for the HMAC hex digest + /// of the body passed with the HTTP requests as an X-Hub-Signature header. + /// + /// + /// The secret. + /// + public string Secret { get; set; } + + /// + /// Gets whether the SSL certificate of the host will be verified when + /// delivering payloads. The default is `false`. + /// + /// + /// true if SSL certificate verification is not performed; + /// otherwise, false. + /// + public bool InsecureSsl { get; set; } + + public override NewOrganizationHook ToRequest() + { + Config = GetWebHookConfig() + .Union(Config, new WebHookConfigComparer()) + .ToDictionary(k => k.Key, v => v.Value); + + return this; + } + + Dictionary GetWebHookConfig() + { + return new Dictionary + { + { "url", Url }, + { "content_type", ContentType.ToParameter() }, + { "secret", Secret }, + { "insecure_ssl", InsecureSsl.ToString() } + }; + } + } + + /// + /// The supported content types for payload serialization. + /// + public enum OrgWebHookContentType + { + Form, + Json + } +} diff --git a/Octokit/Models/Response/OrganizationHook.cs b/Octokit/Models/Response/OrganizationHook.cs new file mode 100644 index 0000000000..bc8224d2f5 --- /dev/null +++ b/Octokit/Models/Response/OrganizationHook.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OrganizationHook + { + public OrganizationHook() { } + + public OrganizationHook(int id, string url, string testUrl, string pingUrl, DateTimeOffset createdAt, DateTimeOffset updatedAt, string name, IReadOnlyList events, bool active, IReadOnlyDictionary config) + { + Url = url; + TestUrl = testUrl; + PingUrl = pingUrl; + CreatedAt = createdAt; + UpdatedAt = updatedAt; + Name = name; + Events = events; + Active = active; + Config = config; + Id = id; + } + + public int Id { get; private set; } + + public string Url { get; private set; } + + [Parameter(Key = "test_url")] + public string TestUrl { get; private set; } + + [Parameter(Key = "ping_url")] + public string PingUrl { get; private set; } + + public DateTimeOffset CreatedAt { get; private set; } + + public DateTimeOffset UpdatedAt { get; private set; } + + public string Name { get; private set; } + + public IReadOnlyList Events { get; private set; } + + public bool Active { get; private set; } + + public IReadOnlyDictionary Config { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "Organization Hook: Name: {0} Url: {1}, Events: {2}", Name, Url, string.Join(", ", Events)); + } + } + } +}