diff --git a/src/Squidex.Events/Schemas/WebhookAdded.cs b/src/Squidex.Events/Schemas/WebhookAdded.cs new file mode 100644 index 000000000..15285c2c7 --- /dev/null +++ b/src/Squidex.Events/Schemas/WebhookAdded.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// WebhookAdded.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Events.Schemas +{ + [TypeName("WebhookAddedEvent")] + public sealed class WebhookAdded : SchemaEvent + { + public Guid Id { get; set; } + + public Uri Url { get; set; } + + public string SecurityToken { get; set; } + } +} diff --git a/src/Squidex.Events/Schemas/WebhookDeleted.cs b/src/Squidex.Events/Schemas/WebhookDeleted.cs new file mode 100644 index 000000000..03ef09004 --- /dev/null +++ b/src/Squidex.Events/Schemas/WebhookDeleted.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// WebhookDeleted.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Events.Schemas +{ + [TypeName("WebhookDeletedEvent")] + public sealed class WebhookDeleted : SchemaEvent + { + public Guid Id { get; set; } + } +} diff --git a/src/Squidex.Infrastructure/RandomHash.cs b/src/Squidex.Infrastructure/RandomHash.cs new file mode 100644 index 000000000..e30007217 --- /dev/null +++ b/src/Squidex.Infrastructure/RandomHash.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// RandomHash.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Squidex.Infrastructure +{ + public static class RandomHash + { + public static string New() + { + using (var sha = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()); + var hash = sha.ComputeHash(bytes); + + return Convert.ToBase64String(hash).Replace("+", "x"); + } + } + } +} diff --git a/src/Squidex.Write/Apps/AppCommandHandler.cs b/src/Squidex.Write/Apps/AppCommandHandler.cs index e7440a2ce..d9965ae49 100644 --- a/src/Squidex.Write/Apps/AppCommandHandler.cs +++ b/src/Squidex.Write/Apps/AppCommandHandler.cs @@ -27,25 +27,21 @@ namespace Squidex.Write.Apps private readonly IAppPlansProvider appPlansProvider; private readonly IAppPlanBillingManager appPlansBillingManager; private readonly IUserResolver userResolver; - private readonly ClientKeyGenerator keyGenerator; public AppCommandHandler( IAggregateHandler handler, IAppRepository appRepository, IAppPlansProvider appPlansProvider, IAppPlanBillingManager appPlansBillingManager, - IUserResolver userResolver, - ClientKeyGenerator keyGenerator) + IUserResolver userResolver) { Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(keyGenerator, nameof(keyGenerator)); Guard.NotNull(appRepository, nameof(appRepository)); Guard.NotNull(userResolver, nameof(userResolver)); Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager)); this.handler = handler; - this.keyGenerator = keyGenerator; this.userResolver = userResolver; this.appRepository = appRepository; this.appPlansProvider = appPlansProvider; @@ -119,12 +115,7 @@ namespace Squidex.Write.Apps protected Task On(AttachClient command, CommandContext context) { - return handler.UpdateAsync(context, a => - { - a.AttachClient(command, keyGenerator.GenerateKey()); - - context.Succeed(EntityCreatedResult.Create(a.Clients[command.Id], a.Version)); - }); + return handler.UpdateAsync(context, a => a.AttachClient(command)); } protected Task On(RemoveContributor command, CommandContext context) diff --git a/src/Squidex.Write/Apps/AppDomainObject.cs b/src/Squidex.Write/Apps/AppDomainObject.cs index b991a0c8a..eadbf3f57 100644 --- a/src/Squidex.Write/Apps/AppDomainObject.cs +++ b/src/Squidex.Write/Apps/AppDomainObject.cs @@ -153,13 +153,13 @@ namespace Squidex.Write.Apps return this; } - public AppDomainObject AttachClient(AttachClient command, string secret) + public AppDomainObject AttachClient(AttachClient command) { Guard.Valid(command, nameof(command), () => "Cannot attach client"); ThrowIfNotCreated(); - RaiseEvent(SimpleMapper.Map(command, new AppClientAttached { Secret = secret })); + RaiseEvent(SimpleMapper.Map(command, new AppClientAttached())); return this; } diff --git a/src/Squidex.Write/Apps/ClientKeyGenerator.cs b/src/Squidex.Write/Apps/ClientKeyGenerator.cs deleted file mode 100644 index a316b9061..000000000 --- a/src/Squidex.Write/Apps/ClientKeyGenerator.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ========================================================================== -// ClientKeyGenerator.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Security.Cryptography; -using System.Text; - -// ReSharper disable ClassWithVirtualMembersNeverInherited.Global - -namespace Squidex.Write.Apps -{ - public class ClientKeyGenerator - { - private readonly Func algorithmFactory; - - public ClientKeyGenerator() - { - algorithmFactory = SHA256.Create; - } - - public virtual string GenerateKey() - { - return Hash(Guid.NewGuid().ToString()); - } - - private string Hash(string input) - { - using (var sha = algorithmFactory()) - { - var bytes = Encoding.UTF8.GetBytes(input); - var hash = sha.ComputeHash(bytes); - - return Convert.ToBase64String(hash).Replace("+", "x"); - } - } - } -} diff --git a/src/Squidex.Write/Apps/Commands/AttachClient.cs b/src/Squidex.Write/Apps/Commands/AttachClient.cs index a84bba7c6..33aeef319 100644 --- a/src/Squidex.Write/Apps/Commands/AttachClient.cs +++ b/src/Squidex.Write/Apps/Commands/AttachClient.cs @@ -15,6 +15,8 @@ namespace Squidex.Write.Apps.Commands { public string Id { get; set; } + public string Secret { get; } = RandomHash.New(); + public void Validate(IList errors) { if (!Id.IsSlug()) diff --git a/src/Squidex.Write/Schemas/Commands/AddWebhook.cs b/src/Squidex.Write/Schemas/Commands/AddWebhook.cs new file mode 100644 index 000000000..bd967f990 --- /dev/null +++ b/src/Squidex.Write/Schemas/Commands/AddWebhook.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// AddWebhook.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Infrastructure; + +namespace Squidex.Write.Schemas.Commands +{ + public sealed class AddWebhook : SchemaAggregateCommand, IValidatable + { + public Guid Id { get; } = Guid.NewGuid(); + + public Uri Url { get; set; } + + public string SecurityToken { get; } = RandomHash.New(); + + public void Validate(IList errors) + { + if (Url == null || !Url.IsAbsoluteUri) + { + errors.Add(new ValidationError("Url must be specified and absolute", nameof(Url))); + } + } + } +} diff --git a/src/Squidex.Write/Schemas/Commands/DeleteWebhook.cs b/src/Squidex.Write/Schemas/Commands/DeleteWebhook.cs new file mode 100644 index 000000000..77242a664 --- /dev/null +++ b/src/Squidex.Write/Schemas/Commands/DeleteWebhook.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// DeleteWebhook.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Write.Schemas.Commands +{ + public class DeleteWebhook : SchemaAggregateCommand + { + public Guid Id { get; set; } + } +} diff --git a/src/Squidex.Write/Schemas/SchemaCommandHandler.cs b/src/Squidex.Write/Schemas/SchemaCommandHandler.cs index b9757c423..7f545284b 100644 --- a/src/Squidex.Write/Schemas/SchemaCommandHandler.cs +++ b/src/Squidex.Write/Schemas/SchemaCommandHandler.cs @@ -60,6 +60,16 @@ namespace Squidex.Write.Schemas }); } + protected Task On(AddWebhook command, CommandContext context) + { + return handler.UpdateAsync(context, s => s.AddWebhook(command)); + } + + protected Task On(DeleteWebhook command, CommandContext context) + { + return handler.UpdateAsync(context, s => s.DeleteWebhook(command)); + } + protected Task On(DeleteSchema command, CommandContext context) { return handler.UpdateAsync(context, s => s.Delete(command)); diff --git a/src/Squidex.Write/Schemas/SchemaDomainObject.cs b/src/Squidex.Write/Schemas/SchemaDomainObject.cs index 3e9577ac6..1ffbb55ef 100644 --- a/src/Squidex.Write/Schemas/SchemaDomainObject.cs +++ b/src/Squidex.Write/Schemas/SchemaDomainObject.cs @@ -23,6 +23,7 @@ namespace Squidex.Write.Schemas public class SchemaDomainObject : DomainObjectBase { private readonly FieldRegistry registry; + private readonly HashSet webhookIds = new HashSet(); private bool isDeleted; private long totalFields; private Schema schema; @@ -107,6 +108,16 @@ namespace Squidex.Write.Schemas schema = SchemaEventDispatcher.Dispatch(@event, schema); } + protected void On(WebhookAdded @event) + { + webhookIds.Add(@event.Id); + } + + protected void On(WebhookDeleted @event) + { + webhookIds.Remove(@event.Id); + } + protected void On(SchemaDeleted @event) { isDeleted = true; @@ -137,6 +148,29 @@ namespace Squidex.Write.Schemas return this; } + public SchemaDomainObject DeleteWebhook(DeleteWebhook command) + { + Guard.NotNull(command, nameof(command)); + + VerifyCreatedAndNotDeleted(); + VerifyWebhookExists(command.Id); + + RaiseEvent(SimpleMapper.Map(command, new WebhookDeleted())); + + return this; + } + + public SchemaDomainObject AddWebhook(AddWebhook command) + { + Guard.Valid(command, nameof(command), () => "Cannot add webhook"); + + VerifyCreatedAndNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new WebhookAdded())); + + return this; + } + public SchemaDomainObject AddField(AddField command) { Guard.Valid(command, nameof(command), () => $"Cannot add field to schema {Id}"); @@ -283,6 +317,14 @@ namespace Squidex.Write.Schemas RaiseEvent(@event); } + private void VerifyWebhookExists(Guid id) + { + if (!webhookIds.Contains(id)) + { + throw new DomainObjectNotFoundException(id.ToString(), "Webhooks", typeof(Schema)); + } + } + private void VerifyNotCreated() { if (schema != null) diff --git a/src/Squidex/Controllers/Api/Apps/AppClientsController.cs b/src/Squidex/Controllers/Api/Apps/AppClientsController.cs index be7643d29..dfc9c5741 100644 --- a/src/Squidex/Controllers/Api/Apps/AppClientsController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppClientsController.cs @@ -15,7 +15,6 @@ using Squidex.Controllers.Api.Apps.Models; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; -using Squidex.Write.Apps; using Squidex.Write.Apps.Commands; namespace Squidex.Controllers.Api.Apps @@ -77,10 +76,11 @@ namespace Squidex.Controllers.Api.Apps [ApiCosts(1)] public async Task PostClient(string app, [FromBody] CreateAppClientDto request) { - var context = await CommandBus.PublishAsync(SimpleMapper.Map(request, new AttachClient())); + var command = SimpleMapper.Map(request, new AttachClient()); - var result = context.Result>().IdOrValue; - var response = SimpleMapper.Map(result, new ClientDto()); + await CommandBus.PublishAsync(command); + + var response = SimpleMapper.Map(command, new ClientDto { Name = command .Id }); return CreatedAtAction(nameof(GetClients), new { app }, response); } diff --git a/src/Squidex/Controllers/ContentApi/ContentsController.cs b/src/Squidex/Controllers/ContentApi/ContentsController.cs index b639bbeca..7cf473cbb 100644 --- a/src/Squidex/Controllers/ContentApi/ContentsController.cs +++ b/src/Squidex/Controllers/ContentApi/ContentsController.cs @@ -97,16 +97,16 @@ namespace Squidex.Controllers.ContentApi return NotFound(); } - var resposne = SimpleMapper.Map(entity, new ContentDto()); + var response = SimpleMapper.Map(entity, new ContentDto()); if (entity.Data != null) { - resposne.Data = entity.Data.ToApiModel(schemaEntity.Schema, App.LanguagesConfig, null, hidden); + response.Data = entity.Data.ToApiModel(schemaEntity.Schema, App.LanguagesConfig, null, hidden); } Response.Headers["ETag"] = new StringValues(entity.Version.ToString()); - return Ok(resposne); + return Ok(response); } [HttpPost] @@ -121,8 +121,6 @@ namespace Squidex.Controllers.ContentApi var result = context.Result>(); var response = ContentDto.Create(command, result); - Response.Headers["ETag"] = new StringValues(response.Version.ToString()); - return CreatedAtAction(nameof(GetContent), new { id = response.Id }, response); } diff --git a/tests/Squidex.Infrastructure.Tests/RandomHashTests.cs b/tests/Squidex.Infrastructure.Tests/RandomHashTests.cs new file mode 100644 index 000000000..3d47d1793 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/RandomHashTests.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// RandomHashTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Xunit; + +namespace Squidex.Infrastructure +{ + public class RandomHashTests + { + [Fact] + public void Should_create_long_hash() + { + var hash = RandomHash.New(); + + Assert.Equal(44, hash.Length); + } + + [Fact] + public void Should_create_new_hashs() + { + var hash1 = RandomHash.New(); + var hash2 = RandomHash.New(); + + Assert.NotEqual(hash1, hash2); + } + } +} diff --git a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs index e4ed735ba..352b9e461 100644 --- a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs +++ b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs @@ -28,7 +28,6 @@ namespace Squidex.Write.Apps { public class AppCommandHandlerTests : HandlerTestBase { - private readonly Mock keyGenerator = new Mock(); private readonly Mock appRepository = new Mock(); private readonly Mock appPlansProvider = new Mock(); private readonly Mock appPlansBillingManager = new Mock(); @@ -37,14 +36,13 @@ namespace Squidex.Write.Apps private readonly AppDomainObject app; private readonly Language language = Language.DE; private readonly string contributorId = Guid.NewGuid().ToString(); - private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientName = "client"; public AppCommandHandlerTests() { app = new AppDomainObject(AppId, -1); - sut = new AppCommandHandler(Handler, appRepository.Object, appPlansProvider.Object, appPlansBillingManager.Object, userResolver.Object, keyGenerator.Object); + sut = new AppCommandHandler(Handler, appRepository.Object, appPlansProvider.Object, appPlansBillingManager.Object, userResolver.Object); } [Fact] @@ -164,22 +162,14 @@ namespace Squidex.Write.Apps [Fact] public async Task AttachClient_should_update_domain_object() { - keyGenerator.Setup(x => x.GenerateKey()) - .Returns(clientSecret) - .Verifiable(); - CreateApp(); - + var context = CreateContextForCommand(new AttachClient { Id = clientName }); await TestUpdate(app, async _ => { await sut.HandleAsync(context); }); - - keyGenerator.VerifyAll(); - - context.Result>().IdOrValue.ShouldBeEquivalentTo(new AppClient(clientName, clientSecret)); } [Fact] @@ -188,7 +178,7 @@ namespace Squidex.Write.Apps appPlansProvider.Setup(x => x.IsConfiguredPlan("my-plan")).Returns(false); CreateApp() - .AttachClient(CreateCommand(new AttachClient { Id = clientName }), clientSecret); + .AttachClient(CreateCommand(new AttachClient { Id = clientName })); var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan" }); @@ -199,30 +189,26 @@ namespace Squidex.Write.Apps } [Fact] - public async Task ChangePlan_should_update_domain_object() + public async Task RenameClient_should_update_domain_object() { - appPlansProvider.Setup(x => x.IsConfiguredPlan("my-plan")).Returns(true); - CreateApp() - .AttachClient(CreateCommand(new AttachClient { Id = clientName }), clientSecret); + .AttachClient(CreateCommand(new AttachClient { Id = clientName })); - var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan" }); + var context = CreateContextForCommand(new RenameClient { Id = clientName, Name = "New Name" }); await TestUpdate(app, async _ => { await sut.HandleAsync(context); }); - - appPlansBillingManager.Verify(x => x.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan"), Times.Once()); } [Fact] - public async Task RenameClient_should_update_domain_object() + public async Task RevokeClient_should_update_domain_object() { CreateApp() - .AttachClient(CreateCommand(new AttachClient { Id = clientName }), clientSecret); + .AttachClient(CreateCommand(new AttachClient { Id = clientName })); - var context = CreateContextForCommand(new RenameClient { Id = clientName, Name = "New Name" }); + var context = CreateContextForCommand(new RevokeClient { Id = clientName }); await TestUpdate(app, async _ => { @@ -231,17 +217,20 @@ namespace Squidex.Write.Apps } [Fact] - public async Task RevokeClient_should_update_domain_object() + public async Task ChangePlan_should_update_domain_object() { - CreateApp() - .AttachClient(CreateCommand(new AttachClient { Id = clientName }), clientSecret); + appPlansProvider.Setup(x => x.IsConfiguredPlan("my-plan")).Returns(true); - var context = CreateContextForCommand(new RevokeClient { Id = clientName }); + CreateApp(); + + var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan" }); await TestUpdate(app, async _ => { await sut.HandleAsync(context); }); + + appPlansBillingManager.Verify(x => x.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan"), Times.Once()); } [Fact] diff --git a/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs index 7c0b272d1..ef362e11a 100644 --- a/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs @@ -25,7 +25,6 @@ namespace Squidex.Write.Apps { private readonly AppDomainObject sut; private readonly string contributorId = Guid.NewGuid().ToString(); - private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientId = "client"; private readonly string clientNewName = "My Client"; private readonly string planId = "premium"; @@ -240,7 +239,7 @@ namespace Squidex.Write.Apps { Assert.Throws(() => { - sut.AttachClient(CreateCommand(new AttachClient { Id = clientId }), clientSecret); + sut.AttachClient(CreateCommand(new AttachClient { Id = clientId })); }); } @@ -251,12 +250,12 @@ namespace Squidex.Write.Apps Assert.Throws(() => { - sut.AttachClient(CreateCommand(new AttachClient()), clientSecret); + sut.AttachClient(CreateCommand(new AttachClient())); }); Assert.Throws(() => { - sut.AttachClient(CreateCommand(new AttachClient { Id = string.Empty }), clientSecret); + sut.AttachClient(CreateCommand(new AttachClient { Id = string.Empty })); }); } @@ -265,24 +264,26 @@ namespace Squidex.Write.Apps { CreateApp(); - sut.AttachClient(CreateCommand(new AttachClient { Id = clientId }), clientSecret); + sut.AttachClient(CreateCommand(new AttachClient { Id = clientId })); Assert.Throws(() => { - sut.AttachClient(CreateCommand(new AttachClient { Id = clientId }), clientSecret); + sut.AttachClient(CreateCommand(new AttachClient { Id = clientId })); }); } [Fact] public void AttachClient_should_create_events() { + var command = new AttachClient { Id = clientId }; + CreateApp(); - sut.AttachClient(CreateCommand(new AttachClient { Id = clientId }), clientSecret); + sut.AttachClient(CreateCommand(command)); sut.GetUncomittedEvents() .ShouldHaveSameEvents( - CreateEvent(new AppClientAttached { Id = clientId, Secret = clientSecret }) + CreateEvent(new AppClientAttached { Id = clientId, Secret = command.Secret }) ); } @@ -565,7 +566,7 @@ namespace Squidex.Write.Apps private void CreateClient() { - sut.AttachClient(CreateCommand(new AttachClient { Id = clientId }), clientSecret); + sut.AttachClient(CreateCommand(new AttachClient { Id = clientId })); ((IAggregate)sut).ClearUncommittedEvents(); } diff --git a/tests/Squidex.Write.Tests/Apps/ClientKeyGeneratorTests.cs b/tests/Squidex.Write.Tests/Apps/ClientKeyGeneratorTests.cs deleted file mode 100644 index d14a8a581..000000000 --- a/tests/Squidex.Write.Tests/Apps/ClientKeyGeneratorTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ========================================================================== -// ClientKeyGeneratorTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Xunit; - -namespace Squidex.Write.Apps -{ - public class ClientKeyGeneratorTests - { - private readonly ClientKeyGenerator sut = new ClientKeyGenerator(); - - [Fact] - public void Should_create_very_long_client_key() - { - var key = sut.GenerateKey(); - - Assert.Equal(44, key.Length); - } - } -} diff --git a/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs b/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs index 50a38d209..c66e50e1d 100644 --- a/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs +++ b/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs @@ -237,6 +237,40 @@ namespace Squidex.Write.Schemas }); } + [Fact] + public async Task AddWebhook_should_update_domain_object() + { + CreateSchema(); + + var context = CreateContextForCommand(new AddWebhook { Url = new Uri("http://cloud.squidex.io") }); + + await TestUpdate(schema, async _ => + { + await sut.HandleAsync(context); + }); + } + + [Fact] + public async Task DeleteWebhook_should_update_domain_object() + { + var createCommand = new AddWebhook { Url = new Uri("http://cloud.squidex.io") }; + + CreateSchema(); + CreateWebhook(createCommand); + + var context = CreateContextForCommand(new DeleteWebhook { Id = createCommand.Id }); + + await TestUpdate(schema, async _ => + { + await sut.HandleAsync(context); + }); + } + + private void CreateWebhook(AddWebhook command) + { + schema.AddWebhook(command); + } + private void CreateSchema() { schema.Create(CreateCommand(new CreateSchema { Name = SchemaName })); diff --git a/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs b/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs index 648df7c5c..f81068274 100644 --- a/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs +++ b/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System; using System.Collections.Generic; using System.Linq; using Moq; @@ -667,6 +668,100 @@ namespace Squidex.Write.Schemas ); } + [Fact] + public void AddWebhook_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.AddWebhook(CreateCommand(new AddWebhook { Url = new Uri("https://cloud.squidex.io") })); + }); + } + + [Fact] + public void AddWebhook_should_throw_exception_if_command_is_not_valid() + { + Assert.Throws(() => + { + sut.AddWebhook(CreateCommand(new AddWebhook())); + }); + } + + [Fact] + public void AddWebhook_should_throw_exception_if_schema_is_deleted() + { + CreateSchema(); + DeleteSchema(); + + Assert.Throws(() => + { + sut.AddWebhook(CreateCommand(new AddWebhook { Url = new Uri("https://cloud.squidex.io") })); + }); + } + + [Fact] + public void AddWebhook_should_update_schema_and_create_events() + { + var command = new AddWebhook { Url = new Uri("https://cloud.squidex.io") }; + + CreateSchema(); + + sut.AddWebhook(CreateCommand(command)); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateEvent(new WebhookAdded { Id = command.Id, Url = command.Url, SecurityToken = command.SecurityToken }) + ); + } + + [Fact] + public void DeleteWebhook_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.DeleteWebhook(CreateCommand(new DeleteWebhook())); + }); + } + + [Fact] + public void DeleteWebhook_should_throw_exception_if_webhook_not_found() + { + CreateSchema(); + + Assert.Throws(() => + { + sut.DeleteWebhook(CreateCommand(new DeleteWebhook { Id = Guid.NewGuid() })); + }); + } + + [Fact] + public void DeleteWebhook_should_throw_exception_if_schema_is_deleted() + { + CreateSchema(); + DeleteSchema(); + + Assert.Throws(() => + { + sut.DeleteWebhook(CreateCommand(new DeleteWebhook { Id = Guid.NewGuid() })); + }); + } + + [Fact] + public void DeleteWebhook_should_update_schema_and_create_events() + { + var createCommand = new AddWebhook { Url = new Uri("https://cloud.squidex.io") }; + + CreateSchema(); + + sut.AddWebhook(CreateCommand(createCommand)); + sut.DeleteWebhook(CreateCommand(new DeleteWebhook { Id = createCommand.Id })); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateEvent(new WebhookAdded { Id = createCommand.Id, Url = createCommand.Url, SecurityToken = createCommand.SecurityToken }), + CreateEvent(new WebhookDeleted { Id = createCommand.Id }) + ); + } + private void CreateField() { sut.AddField(new AddField { Name = fieldName, Properties = new NumberFieldProperties() });