mirror of https://github.com/Squidex/squidex.git
64 changed files with 1374 additions and 1747 deletions
@ -1,25 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookSchema.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Webhooks |
|
||||
{ |
|
||||
public sealed class WebhookSchema |
|
||||
{ |
|
||||
public Guid SchemaId { get; set; } |
|
||||
|
|
||||
public bool SendCreate { get; set; } |
|
||||
|
|
||||
public bool SendUpdate { get; set; } |
|
||||
|
|
||||
public bool SendDelete { get; set; } |
|
||||
|
|
||||
public bool SendPublish { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookCreated.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Events.Webhooks |
|
||||
{ |
|
||||
[EventType(nameof(WebhookCreated))] |
|
||||
public sealed class WebhookCreated : WebhookEditEvent |
|
||||
{ |
|
||||
public string SharedSecret { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookDeleted.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Events.Webhooks |
|
||||
{ |
|
||||
[EventType(nameof(WebhookDeleted), 2)] |
|
||||
public sealed class WebhookDeleted : WebhookEvent |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookEditEvent.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Events.Webhooks |
|
||||
{ |
|
||||
public abstract class WebhookEditEvent : WebhookEvent |
|
||||
{ |
|
||||
public Uri Url { get; set; } |
|
||||
|
|
||||
public List<WebhookSchema> Schemas { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookUpdated.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Events.Webhooks |
|
||||
{ |
|
||||
[EventType(nameof(WebhookUpdated))] |
|
||||
public sealed class WebhookUpdated : WebhookEditEvent |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,41 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoRuleEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using MongoDB.Bson.Serialization.Attributes; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Read.Rules; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.MongoDb; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.MongoDb.Rules |
||||
|
{ |
||||
|
public class MongoRuleEntity : MongoEntity, IRuleEntity |
||||
|
{ |
||||
|
[BsonRequired] |
||||
|
[BsonElement] |
||||
|
public Guid AppId { get; set; } |
||||
|
|
||||
|
[BsonRequired] |
||||
|
[BsonElement] |
||||
|
public RefToken CreatedBy { get; set; } |
||||
|
|
||||
|
[BsonRequired] |
||||
|
[BsonElement] |
||||
|
public RefToken LastModifiedBy { get; set; } |
||||
|
|
||||
|
[BsonRequired] |
||||
|
[BsonElement] |
||||
|
public long Version { get; set; } |
||||
|
|
||||
|
[BsonRequired] |
||||
|
[BsonElement] |
||||
|
[BsonJson] |
||||
|
public Rule Rule { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoRuleRepository.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Bson; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Domain.Apps.Read.Rules; |
||||
|
using Squidex.Domain.Apps.Read.Rules.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.MongoDb; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.MongoDb.Rules |
||||
|
{ |
||||
|
public partial class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository, IEventConsumer |
||||
|
{ |
||||
|
private static readonly List<IRuleEntity> EmptyRules = new List<IRuleEntity>(); |
||||
|
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); |
||||
|
private Dictionary<Guid, List<IRuleEntity>> inMemoryWebhooks; |
||||
|
|
||||
|
public MongoRuleRepository(IMongoDatabase database) |
||||
|
: base(database) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override string CollectionName() |
||||
|
{ |
||||
|
return "Projections_Rules"; |
||||
|
} |
||||
|
|
||||
|
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection) |
||||
|
{ |
||||
|
return Task.WhenAll(collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId))); |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<IRuleEntity>> QueryByAppAsync(Guid appId) |
||||
|
{ |
||||
|
var entities = |
||||
|
await Collection.Find(x => x.AppId == appId) |
||||
|
.ToListAsync(); |
||||
|
|
||||
|
return entities.OfType<IRuleEntity>().ToList(); |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<IRuleEntity>> QueryCachedByAppAsync(Guid appId) |
||||
|
{ |
||||
|
await EnsureRulesLoadedAsync(); |
||||
|
|
||||
|
return inMemoryWebhooks.GetOrDefault(appId) ?? EmptyRules; |
||||
|
} |
||||
|
|
||||
|
private async Task EnsureRulesLoadedAsync() |
||||
|
{ |
||||
|
if (inMemoryWebhooks == null) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await lockObject.WaitAsync(); |
||||
|
|
||||
|
if (inMemoryWebhooks == null) |
||||
|
{ |
||||
|
inMemoryWebhooks = new Dictionary<Guid, List<IRuleEntity>>(); |
||||
|
|
||||
|
var webhooks = |
||||
|
await Collection.Find(new BsonDocument()) |
||||
|
.ToListAsync(); |
||||
|
|
||||
|
foreach (var webhook in webhooks) |
||||
|
{ |
||||
|
inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
lockObject.Release(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoRuleRepository_EventHandling.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Domain.Apps.Events.Rules; |
||||
|
using Squidex.Domain.Apps.Events.Rules.Utils; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.MongoDb.Rules |
||||
|
{ |
||||
|
public partial class MongoRuleRepository |
||||
|
{ |
||||
|
public string Name |
||||
|
{ |
||||
|
get { return GetType().Name; } |
||||
|
} |
||||
|
|
||||
|
public string EventsFilter |
||||
|
{ |
||||
|
get { return "^rules-"; } |
||||
|
} |
||||
|
|
||||
|
public Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(RuleCreated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
await EnsureRulesLoadedAsync(); |
||||
|
|
||||
|
await Collection.CreateAsync(@event, headers, w => |
||||
|
{ |
||||
|
w.Rule = RuleEventDispatcher.Create(@event); |
||||
|
|
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(RuleUpdated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
await EnsureRulesLoadedAsync(); |
||||
|
|
||||
|
await Collection.UpdateAsync(@event, headers, w => |
||||
|
{ |
||||
|
w.Rule.Apply(@event); |
||||
|
|
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(RuleEnabled @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
await EnsureRulesLoadedAsync(); |
||||
|
|
||||
|
await Collection.UpdateAsync(@event, headers, w => |
||||
|
{ |
||||
|
w.Rule.Apply(@event); |
||||
|
|
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(RuleDisabled @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
await EnsureRulesLoadedAsync(); |
||||
|
|
||||
|
await Collection.UpdateAsync(@event, headers, w => |
||||
|
{ |
||||
|
w.Rule.Apply(@event); |
||||
|
|
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
||||
|
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(RuleDeleted @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
await EnsureRulesLoadedAsync(); |
||||
|
|
||||
|
inMemoryWebhooks.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.RuleId); |
||||
|
|
||||
|
await Collection.DeleteManyAsync(x => x.Id == @event.RuleId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,74 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// MongoWebhookEntity.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using MongoDB.Bson.Serialization.Attributes; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
using Squidex.Domain.Apps.Read.Webhooks; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.MongoDb; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks |
|
||||
{ |
|
||||
public class MongoWebhookEntity : MongoEntity, IWebhookEntity |
|
||||
{ |
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public Uri Url { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public Guid AppId { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long Version { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public RefToken CreatedBy { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public RefToken LastModifiedBy { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public string SharedSecret { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long TotalSucceeded { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long TotalFailed { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long TotalTimedout { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long TotalRequestTime { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public List<WebhookSchema> Schemas { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public List<Guid> SchemaIds { get; set; } |
|
||||
|
|
||||
IEnumerable<WebhookSchema> IWebhookEntity.Schemas |
|
||||
{ |
|
||||
get { return Schemas; } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,119 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// MongoWebhookRepository.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using MongoDB.Bson; |
|
||||
using MongoDB.Driver; |
|
||||
using Squidex.Domain.Apps.Read.Webhooks; |
|
||||
using Squidex.Domain.Apps.Read.Webhooks.Repositories; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
using Squidex.Infrastructure.MongoDb; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks |
|
||||
{ |
|
||||
public partial class MongoWebhookRepository : MongoRepositoryBase<MongoWebhookEntity>, IWebhookRepository, IEventConsumer |
|
||||
{ |
|
||||
private static readonly List<IWebhookEntity> EmptyWebhooks = new List<IWebhookEntity>(); |
|
||||
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); |
|
||||
private Dictionary<Guid, List<IWebhookEntity>> inMemoryWebhooks; |
|
||||
|
|
||||
public MongoWebhookRepository(IMongoDatabase database) |
|
||||
: base(database) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override string CollectionName() |
|
||||
{ |
|
||||
return "Projections_SchemaWebhooks"; |
|
||||
} |
|
||||
|
|
||||
protected override Task SetupCollectionAsync(IMongoCollection<MongoWebhookEntity> collection) |
|
||||
{ |
|
||||
return Task.WhenAll( |
|
||||
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)), |
|
||||
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SchemaIds))); |
|
||||
} |
|
||||
|
|
||||
public async Task<IReadOnlyList<IWebhookEntity>> QueryByAppAsync(Guid appId) |
|
||||
{ |
|
||||
var entities = |
|
||||
await Collection.Find(x => x.AppId == appId) |
|
||||
.ToListAsync(); |
|
||||
|
|
||||
return entities.OfType<IWebhookEntity>().ToList(); |
|
||||
} |
|
||||
|
|
||||
public async Task<IReadOnlyList<IWebhookEntity>> QueryCachedByAppAsync(Guid appId) |
|
||||
{ |
|
||||
await EnsureWebooksLoadedAsync(); |
|
||||
|
|
||||
return inMemoryWebhooks.GetOrDefault(appId) ?? EmptyWebhooks; |
|
||||
} |
|
||||
|
|
||||
public async Task TraceSentAsync(Guid webhookId, WebhookResult result, TimeSpan elapsed) |
|
||||
{ |
|
||||
var webhookEntity = |
|
||||
await Collection.Find(x => x.Id == webhookId) |
|
||||
.FirstOrDefaultAsync(); |
|
||||
|
|
||||
if (webhookEntity != null) |
|
||||
{ |
|
||||
switch (result) |
|
||||
{ |
|
||||
case WebhookResult.Success: |
|
||||
webhookEntity.TotalSucceeded++; |
|
||||
break; |
|
||||
case WebhookResult.Failed: |
|
||||
webhookEntity.TotalFailed++; |
|
||||
break; |
|
||||
case WebhookResult.Timeout: |
|
||||
webhookEntity.TotalTimedout++; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
webhookEntity.TotalRequestTime += (long)elapsed.TotalMilliseconds; |
|
||||
|
|
||||
await Collection.ReplaceOneAsync(x => x.Id == webhookId, webhookEntity); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task EnsureWebooksLoadedAsync() |
|
||||
{ |
|
||||
if (inMemoryWebhooks == null) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
await lockObject.WaitAsync(); |
|
||||
|
|
||||
if (inMemoryWebhooks == null) |
|
||||
{ |
|
||||
inMemoryWebhooks = new Dictionary<Guid, List<IWebhookEntity>>(); |
|
||||
|
|
||||
var webhooks = |
|
||||
await Collection.Find(new BsonDocument()) |
|
||||
.ToListAsync(); |
|
||||
|
|
||||
foreach (var webhook in webhooks) |
|
||||
{ |
|
||||
inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
lockObject.Release(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,99 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// MongoSchemaWebhookRepository_EventHandling.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using MongoDB.Driver; |
|
||||
using Squidex.Domain.Apps.Events.Schemas; |
|
||||
using Squidex.Domain.Apps.Events.Webhooks; |
|
||||
using Squidex.Domain.Apps.Read.MongoDb.Utils; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
using Squidex.Infrastructure.Dispatching; |
|
||||
using Squidex.Infrastructure.Reflection; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks |
|
||||
{ |
|
||||
public partial class MongoWebhookRepository |
|
||||
{ |
|
||||
public string Name |
|
||||
{ |
|
||||
get { return GetType().Name; } |
|
||||
} |
|
||||
|
|
||||
public string EventsFilter |
|
||||
{ |
|
||||
get { return "(^webhook-)|(^schema-)"; } |
|
||||
} |
|
||||
|
|
||||
public Task On(Envelope<IEvent> @event) |
|
||||
{ |
|
||||
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
|
||||
} |
|
||||
|
|
||||
protected async Task On(WebhookCreated @event, EnvelopeHeaders headers) |
|
||||
{ |
|
||||
await EnsureWebooksLoadedAsync(); |
|
||||
|
|
||||
await Collection.CreateAsync(@event, headers, w => |
|
||||
{ |
|
||||
SimpleMapper.Map(@event, w); |
|
||||
|
|
||||
w.SchemaIds = w.Schemas.Select(x => x.SchemaId).ToList(); |
|
||||
|
|
||||
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|
||||
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
protected async Task On(WebhookUpdated @event, EnvelopeHeaders headers) |
|
||||
{ |
|
||||
await EnsureWebooksLoadedAsync(); |
|
||||
|
|
||||
await Collection.UpdateAsync(@event, headers, w => |
|
||||
{ |
|
||||
SimpleMapper.Map(@event, w); |
|
||||
|
|
||||
w.SchemaIds = w.Schemas.Select(x => x.SchemaId).ToList(); |
|
||||
|
|
||||
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|
||||
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers) |
|
||||
{ |
|
||||
await EnsureWebooksLoadedAsync(); |
|
||||
|
|
||||
var webhooks = |
|
||||
await Collection.Find(t => t.SchemaIds.Contains(@event.SchemaId.Id)) |
|
||||
.ToListAsync(); |
|
||||
|
|
||||
foreach (var webhook in webhooks) |
|
||||
{ |
|
||||
webhook.Schemas.RemoveAll(s => s.SchemaId == @event.SchemaId.Id); |
|
||||
|
|
||||
webhook.SchemaIds = webhook.Schemas.Select(x => x.SchemaId).ToList(); |
|
||||
|
|
||||
inMemoryWebhooks.GetOrAddNew(webhook.AppId).RemoveAll(x => x.Id == webhook.Id); |
|
||||
inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); |
|
||||
|
|
||||
await Collection.ReplaceOneAsync(x => x.Id == webhook.Id, webhook); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected async Task On(WebhookDeleted @event, EnvelopeHeaders headers) |
|
||||
{ |
|
||||
await EnsureWebooksLoadedAsync(); |
|
||||
|
|
||||
inMemoryWebhooks.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.WebhookId); |
|
||||
|
|
||||
await Collection.DeleteManyAsync(x => x.Id == @event.WebhookId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,31 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// IWebhookEntity.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
|
||||
{ |
|
||||
public interface IWebhookEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion |
|
||||
{ |
|
||||
Uri Url { get; } |
|
||||
|
|
||||
long TotalSucceeded { get; } |
|
||||
|
|
||||
long TotalFailed { get; } |
|
||||
|
|
||||
long TotalTimedout { get; } |
|
||||
|
|
||||
long TotalRequestTime { get; } |
|
||||
|
|
||||
string SharedSecret { get; } |
|
||||
|
|
||||
IEnumerable<WebhookSchema> Schemas { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,27 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// IWebhookEventEntity.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using NodaTime; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
|
||||
{ |
|
||||
public interface IWebhookEventEntity : IEntity |
|
||||
{ |
|
||||
WebhookJob Job { get; } |
|
||||
|
|
||||
Instant? NextAttempt { get; } |
|
||||
|
|
||||
WebhookResult Result { get; } |
|
||||
|
|
||||
WebhookJobResult JobResult { get; } |
|
||||
|
|
||||
int NumCalls { get; } |
|
||||
|
|
||||
string LastDump { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,35 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// IWebhookEventRepository.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using NodaTime; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks.Repositories |
|
||||
{ |
|
||||
public interface IWebhookEventRepository |
|
||||
{ |
|
||||
Task EnqueueAsync(WebhookJob job, Instant nextAttempt); |
|
||||
|
|
||||
Task EnqueueAsync(Guid id, Instant nextAttempt); |
|
||||
|
|
||||
Task TraceSendingAsync(Guid jobId); |
|
||||
|
|
||||
Task TraceSentAsync(Guid jobId, string dump, WebhookResult result, TimeSpan elapsed, Instant? nextCall); |
|
||||
|
|
||||
Task QueryPendingAsync(Func<IWebhookEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken)); |
|
||||
|
|
||||
Task<int> CountByAppAsync(Guid appId); |
|
||||
|
|
||||
Task<IReadOnlyList<IWebhookEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20); |
|
||||
|
|
||||
Task<IWebhookEventEntity> FindAsync(Guid id); |
|
||||
} |
|
||||
} |
|
||||
@ -1,23 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ISchemaWebhookRepository.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks.Repositories |
|
||||
{ |
|
||||
public interface IWebhookRepository |
|
||||
{ |
|
||||
Task TraceSentAsync(Guid webhookId, WebhookResult result, TimeSpan elapsed); |
|
||||
|
|
||||
Task<IReadOnlyList<IWebhookEntity>> QueryByAppAsync(Guid appId); |
|
||||
|
|
||||
Task<IReadOnlyList<IWebhookEntity>> QueryCachedByAppAsync(Guid appId); |
|
||||
} |
|
||||
} |
|
||||
@ -1,160 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookDequeuer.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using System.Threading.Tasks.Dataflow; |
|
||||
using NodaTime; |
|
||||
using Squidex.Domain.Apps.Read.Webhooks.Repositories; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
using Squidex.Infrastructure.Timers; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
|
||||
{ |
|
||||
public sealed class WebhookDequeuer : DisposableObjectBase, IExternalSystem |
|
||||
{ |
|
||||
private readonly ActionBlock<IWebhookEventEntity> requestBlock; |
|
||||
private readonly TransformBlock<IWebhookEventEntity, IWebhookEventEntity> blockBlock; |
|
||||
private readonly IWebhookEventRepository webhookEventRepository; |
|
||||
private readonly IWebhookRepository webhookRepository; |
|
||||
private readonly WebhookSender webhookSender; |
|
||||
private readonly CompletionTimer timer; |
|
||||
private readonly ISemanticLog log; |
|
||||
private readonly IClock clock; |
|
||||
|
|
||||
public WebhookDequeuer(WebhookSender webhookSender, |
|
||||
IWebhookEventRepository webhookEventRepository, |
|
||||
IWebhookRepository webhookRepository, |
|
||||
IClock clock, |
|
||||
ISemanticLog log) |
|
||||
{ |
|
||||
Guard.NotNull(webhookEventRepository, nameof(webhookEventRepository)); |
|
||||
Guard.NotNull(webhookRepository, nameof(webhookRepository)); |
|
||||
Guard.NotNull(webhookSender, nameof(webhookSender)); |
|
||||
Guard.NotNull(clock, nameof(clock)); |
|
||||
Guard.NotNull(log, nameof(log)); |
|
||||
|
|
||||
this.webhookEventRepository = webhookEventRepository; |
|
||||
this.webhookRepository = webhookRepository; |
|
||||
this.webhookSender = webhookSender; |
|
||||
|
|
||||
this.clock = clock; |
|
||||
|
|
||||
this.log = log; |
|
||||
|
|
||||
requestBlock = |
|
||||
new ActionBlock<IWebhookEventEntity>(MakeRequestAsync, |
|
||||
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 }); |
|
||||
|
|
||||
blockBlock = |
|
||||
new TransformBlock<IWebhookEventEntity, IWebhookEventEntity>(x => BlockAsync(x), |
|
||||
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1, BoundedCapacity = 1 }); |
|
||||
|
|
||||
blockBlock.LinkTo(requestBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
|
||||
|
|
||||
timer = new CompletionTimer(5000, QueryAsync); |
|
||||
} |
|
||||
|
|
||||
protected override void DisposeObject(bool disposing) |
|
||||
{ |
|
||||
if (disposing) |
|
||||
{ |
|
||||
timer.StopAsync().Wait(); |
|
||||
|
|
||||
blockBlock.Complete(); |
|
||||
requestBlock.Completion.Wait(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void Connect() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public void Next() |
|
||||
{ |
|
||||
timer.SkipCurrentDelay(); |
|
||||
} |
|
||||
|
|
||||
private async Task QueryAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
await webhookEventRepository.QueryPendingAsync(blockBlock.SendAsync, cancellationToken); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
log.LogError(ex, w => w |
|
||||
.WriteProperty("action", "QueueWebhookEvents") |
|
||||
.WriteProperty("status", "Failed")); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task<IWebhookEventEntity> BlockAsync(IWebhookEventEntity @event) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
await webhookEventRepository.TraceSendingAsync(@event.Id); |
|
||||
|
|
||||
return @event; |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
log.LogError(ex, w => w |
|
||||
.WriteProperty("action", "BlockWebhookEvent") |
|
||||
.WriteProperty("status", "Failed")); |
|
||||
|
|
||||
throw; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task MakeRequestAsync(IWebhookEventEntity @event) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
var response = await webhookSender.SendAsync(@event.Job); |
|
||||
|
|
||||
Instant? nextCall = null; |
|
||||
|
|
||||
if (response.Result != WebhookResult.Success) |
|
||||
{ |
|
||||
var now = clock.GetCurrentInstant(); |
|
||||
|
|
||||
switch (@event.NumCalls) |
|
||||
{ |
|
||||
case 0: |
|
||||
nextCall = now.Plus(Duration.FromMinutes(5)); |
|
||||
break; |
|
||||
case 1: |
|
||||
nextCall = now.Plus(Duration.FromHours(1)); |
|
||||
break; |
|
||||
case 2: |
|
||||
nextCall = now.Plus(Duration.FromHours(5)); |
|
||||
break; |
|
||||
case 3: |
|
||||
nextCall = now.Plus(Duration.FromHours(6)); |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
await Task.WhenAll( |
|
||||
webhookRepository.TraceSentAsync(@event.Job.WebhookId, response.Result, response.Elapsed), |
|
||||
webhookEventRepository.TraceSentAsync(@event.Id, response.Dump, response.Result, response.Elapsed, nextCall)); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
log.LogError(ex, w => w |
|
||||
.WriteProperty("action", "SendWebhookEvent") |
|
||||
.WriteProperty("status", "Failed")); |
|
||||
|
|
||||
throw; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,140 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookEnqueuer.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using Newtonsoft.Json; |
|
||||
using Newtonsoft.Json.Linq; |
|
||||
using NodaTime; |
|
||||
using Squidex.Domain.Apps.Core.Contents; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
using Squidex.Domain.Apps.Events; |
|
||||
using Squidex.Domain.Apps.Events.Contents; |
|
||||
using Squidex.Domain.Apps.Read.Webhooks.Repositories; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
using Squidex.Infrastructure.Tasks; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
|
||||
{ |
|
||||
public sealed class WebhookEnqueuer : IEventConsumer |
|
||||
{ |
|
||||
private const string ContentPrefix = "Content"; |
|
||||
private static readonly Duration ExpirationTime = Duration.FromDays(2); |
|
||||
private readonly IWebhookEventRepository webhookEventRepository; |
|
||||
private readonly IWebhookRepository webhookRepository; |
|
||||
private readonly IClock clock; |
|
||||
private readonly TypeNameRegistry typeNameRegistry; |
|
||||
private readonly JsonSerializer webhookSerializer; |
|
||||
|
|
||||
public string Name |
|
||||
{ |
|
||||
get { return GetType().Name; } |
|
||||
} |
|
||||
|
|
||||
public string EventsFilter |
|
||||
{ |
|
||||
get { return "^content-"; } |
|
||||
} |
|
||||
|
|
||||
public WebhookEnqueuer(TypeNameRegistry typeNameRegistry, |
|
||||
IWebhookEventRepository webhookEventRepository, |
|
||||
IWebhookRepository webhookRepository, |
|
||||
IClock clock, |
|
||||
JsonSerializer webhookSerializer) |
|
||||
{ |
|
||||
Guard.NotNull(webhookEventRepository, nameof(webhookEventRepository)); |
|
||||
Guard.NotNull(webhookSerializer, nameof(webhookSerializer)); |
|
||||
Guard.NotNull(webhookRepository, nameof(webhookRepository)); |
|
||||
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); |
|
||||
Guard.NotNull(clock, nameof(clock)); |
|
||||
|
|
||||
this.webhookEventRepository = webhookEventRepository; |
|
||||
this.webhookSerializer = webhookSerializer; |
|
||||
this.webhookRepository = webhookRepository; |
|
||||
|
|
||||
this.clock = clock; |
|
||||
|
|
||||
this.typeNameRegistry = typeNameRegistry; |
|
||||
} |
|
||||
|
|
||||
public Task ClearAsync() |
|
||||
{ |
|
||||
return TaskHelper.Done; |
|
||||
} |
|
||||
|
|
||||
public async Task On(Envelope<IEvent> @event) |
|
||||
{ |
|
||||
if (@event.Payload is ContentEvent contentEvent) |
|
||||
{ |
|
||||
var eventType = typeNameRegistry.GetName(@event.Payload.GetType()); |
|
||||
|
|
||||
var webhooks = await webhookRepository.QueryCachedByAppAsync(contentEvent.AppId.Id); |
|
||||
|
|
||||
var matchingWebhooks = webhooks.Where(w => w.Schemas.Any(s => Matchs(s, contentEvent))).ToList(); |
|
||||
|
|
||||
if (matchingWebhooks.Count > 0) |
|
||||
{ |
|
||||
var now = clock.GetCurrentInstant(); |
|
||||
|
|
||||
var eventPayload = CreatePayload(@event, eventType); |
|
||||
var eventName = $"{contentEvent.SchemaId.Name.ToPascalCase()}{CreateContentEventName(eventType)}"; |
|
||||
|
|
||||
foreach (var webhook in matchingWebhooks) |
|
||||
{ |
|
||||
await EnqueueJobAsync(eventPayload, webhook, contentEvent, eventName, now); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task EnqueueJobAsync(string payload, IWebhookEntity webhook, AppEvent contentEvent, string eventName, Instant now) |
|
||||
{ |
|
||||
var signature = $"{payload}{webhook.SharedSecret}".Sha256Base64(); |
|
||||
|
|
||||
var job = new WebhookJob |
|
||||
{ |
|
||||
Id = Guid.NewGuid(), |
|
||||
AppId = contentEvent.AppId.Id, |
|
||||
RequestUrl = webhook.Url, |
|
||||
RequestBody = payload, |
|
||||
RequestSignature = signature, |
|
||||
EventName = eventName, |
|
||||
Expires = now.Plus(ExpirationTime), |
|
||||
WebhookId = webhook.Id |
|
||||
}; |
|
||||
|
|
||||
await webhookEventRepository.EnqueueAsync(job, now); |
|
||||
} |
|
||||
|
|
||||
private static bool Matchs(WebhookSchema webhookSchema, SchemaEvent @event) |
|
||||
{ |
|
||||
return |
|
||||
(@event.SchemaId.Id == webhookSchema.SchemaId) && |
|
||||
(webhookSchema.SendCreate && @event is ContentCreated || |
|
||||
webhookSchema.SendUpdate && @event is ContentUpdated || |
|
||||
webhookSchema.SendDelete && @event is ContentDeleted || |
|
||||
webhookSchema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published); |
|
||||
} |
|
||||
|
|
||||
private string CreatePayload(Envelope<IEvent> @event, string eventType) |
|
||||
{ |
|
||||
return new JObject( |
|
||||
new JProperty("type", eventType), |
|
||||
new JProperty("payload", JObject.FromObject(@event.Payload, webhookSerializer)), |
|
||||
new JProperty("timestamp", @event.Headers.Timestamp().ToString())) |
|
||||
.ToString(Formatting.Indented); |
|
||||
} |
|
||||
|
|
||||
private static string CreateContentEventName(string eventType) |
|
||||
{ |
|
||||
return eventType.StartsWith(ContentPrefix, StringComparison.Ordinal) ? eventType.Substring(ContentPrefix.Length) : eventType; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookJob.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using NodaTime; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
|
||||
{ |
|
||||
public sealed class WebhookJob |
|
||||
{ |
|
||||
public Guid Id { get; set; } |
|
||||
|
|
||||
public Guid AppId { get; set; } |
|
||||
|
|
||||
public Guid WebhookId { get; set; } |
|
||||
|
|
||||
public Uri RequestUrl { get; set; } |
|
||||
|
|
||||
public string RequestBody { get; set; } |
|
||||
|
|
||||
public string RequestSignature { get; set; } |
|
||||
|
|
||||
public string EventName { get; set; } |
|
||||
|
|
||||
public Instant Expires { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,98 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookSender.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Diagnostics; |
|
||||
using System.Net.Http; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
using Squidex.Infrastructure.Http; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
|
||||
{ |
|
||||
public class WebhookSender |
|
||||
{ |
|
||||
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); |
|
||||
|
|
||||
public virtual async Task<(string Dump, WebhookResult Result, TimeSpan Elapsed)> SendAsync(WebhookJob job) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
var request = BuildRequest(job); |
|
||||
HttpResponseMessage response = null; |
|
||||
|
|
||||
var responseString = string.Empty; |
|
||||
|
|
||||
var isTimeout = false; |
|
||||
|
|
||||
var watch = Stopwatch.StartNew(); |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
using (var client = new HttpClient { Timeout = Timeout }) |
|
||||
{ |
|
||||
response = await client.SendAsync(request); |
|
||||
} |
|
||||
} |
|
||||
catch (TimeoutException) |
|
||||
{ |
|
||||
isTimeout = true; |
|
||||
} |
|
||||
catch (OperationCanceledException) |
|
||||
{ |
|
||||
isTimeout = true; |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
responseString = ex.Message; |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
watch.Stop(); |
|
||||
} |
|
||||
|
|
||||
if (response != null) |
|
||||
{ |
|
||||
responseString = await response.Content.ReadAsStringAsync(); |
|
||||
} |
|
||||
|
|
||||
var dump = DumpFormatter.BuildDump(request, response, job.RequestBody, responseString, watch.Elapsed, isTimeout); |
|
||||
|
|
||||
var result = WebhookResult.Failed; |
|
||||
|
|
||||
if (isTimeout) |
|
||||
{ |
|
||||
result = WebhookResult.Timeout; |
|
||||
} |
|
||||
else if (response?.IsSuccessStatusCode == true) |
|
||||
{ |
|
||||
result = WebhookResult.Success; |
|
||||
} |
|
||||
|
|
||||
return (dump, result, watch.Elapsed); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
return (ex.Message, WebhookResult.Failed, TimeSpan.Zero); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static HttpRequestMessage BuildRequest(WebhookJob job) |
|
||||
{ |
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) |
|
||||
{ |
|
||||
Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json") |
|
||||
}; |
|
||||
|
|
||||
request.Headers.Add("X-Signature", job.RequestSignature); |
|
||||
request.Headers.Add("User-Agent", "Squidex Webhook"); |
|
||||
|
|
||||
return request; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,14 +1,14 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// UpdateWebhook.cs
|
// CreateRule.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks.Commands |
namespace Squidex.Domain.Apps.Write.Rules.Commands |
||||
{ |
{ |
||||
public sealed class UpdateWebhook : WebhookEditCommand |
public sealed class CreateRule : RuleEditCommand |
||||
{ |
{ |
||||
} |
} |
||||
} |
} |
||||
@ -1,14 +1,14 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// DeleteWebhook.cs
|
// DeleteRule.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks.Commands |
namespace Squidex.Domain.Apps.Write.Rules.Commands |
||||
{ |
{ |
||||
public sealed class DeleteWebhook : WebhookAggregateCommand |
public sealed class DeleteRule : RuleAggregateCommand |
||||
{ |
{ |
||||
} |
} |
||||
} |
} |
||||
@ -1,18 +1,14 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// WebhookResult.cs
|
// DisableRule.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
namespace Squidex.Domain.Apps.Write.Rules.Commands |
||||
{ |
{ |
||||
public enum WebhookResult |
public sealed class DisableRule : RuleAggregateCommand |
||||
{ |
{ |
||||
Pending, |
|
||||
Success, |
|
||||
Failed, |
|
||||
Timeout |
|
||||
} |
} |
||||
} |
} |
||||
@ -1,18 +1,14 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// WebhookJobResult.cs
|
// EnableRule.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Read.Webhooks |
namespace Squidex.Domain.Apps.Write.Rules.Commands |
||||
{ |
{ |
||||
public enum WebhookJobResult |
public sealed class EnableRule : RuleAggregateCommand |
||||
{ |
{ |
||||
Pending, |
|
||||
Success, |
|
||||
Retry, |
|
||||
Failed |
|
||||
} |
} |
||||
} |
} |
||||
@ -1,17 +1,19 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// WebhookEvent.cs
|
// RuleEditCommand.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
using System; |
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
namespace Squidex.Domain.Apps.Events.Webhooks |
namespace Squidex.Domain.Apps.Write.Rules.Commands |
||||
{ |
{ |
||||
public abstract class WebhookEvent : AppEvent |
public abstract class RuleEditCommand : RuleAggregateCommand |
||||
{ |
{ |
||||
public Guid WebhookId { get; set; } |
public RuleTrigger Trigger { get; set; } |
||||
|
|
||||
|
public RuleAction Action { get; set; } |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// UpdateRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules.Commands |
||||
|
{ |
||||
|
public sealed class UpdateRule : RuleEditCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Read.Schemas.Services; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules.Guards |
||||
|
{ |
||||
|
public static class GuardRule |
||||
|
{ |
||||
|
public static Task CanCreate(CreateRule command, ISchemaProvider schemas) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
return Validate.It(() => "Cannot create rule.", async error => |
||||
|
{ |
||||
|
if (command.Trigger == null) |
||||
|
{ |
||||
|
error(new ValidationError("Trigger must be defined.", nameof(command.Trigger))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
|
||||
|
if (command.Action == null) |
||||
|
{ |
||||
|
error(new ValidationError("Trigger must be defined.", nameof(command.Action))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var errors = await RuleActionValidator.ValidateAsync(command.Action); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static Task CanUpdate(UpdateRule command, ISchemaProvider schemas) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
return Validate.It(() => "Cannot update rule.", async error => |
||||
|
{ |
||||
|
if (command.Trigger == null && command.Action == null) |
||||
|
{ |
||||
|
error(new ValidationError("Either trigger or action must be defined.", nameof(command.Trigger), nameof(command.Action))); |
||||
|
} |
||||
|
|
||||
|
if (command.Trigger != null) |
||||
|
{ |
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
|
||||
|
if (command.Action != null) |
||||
|
{ |
||||
|
var errors = await RuleActionValidator.ValidateAsync(command.Action); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanEnable(EnableRule command, Rule rule) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot enable rule.", error => |
||||
|
{ |
||||
|
if (rule.IsEnabled) |
||||
|
{ |
||||
|
error(new ValidationError("Rule is already enabled.")); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanDisable(DisableRule command, Rule rule) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot disable rule.", error => |
||||
|
{ |
||||
|
if (!rule.IsEnabled) |
||||
|
{ |
||||
|
error(new ValidationError("Rule is already disabled.")); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanDelete(DeleteRule command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleActionValidator.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules.Guards |
||||
|
{ |
||||
|
public sealed class RuleActionValidator : IRuleActionVisitor<Task<IEnumerable<ValidationError>>> |
||||
|
{ |
||||
|
public static Task<IEnumerable<ValidationError>> ValidateAsync(RuleAction action) |
||||
|
{ |
||||
|
Guard.NotNull(action, nameof(action)); |
||||
|
|
||||
|
var visitor = new RuleActionValidator(); |
||||
|
|
||||
|
return action.Accept(visitor); |
||||
|
} |
||||
|
|
||||
|
public Task<IEnumerable<ValidationError>> Visit(WebhookAction action) |
||||
|
{ |
||||
|
var errors = new List<ValidationError>(); |
||||
|
|
||||
|
if (action.Url == null || !action.Url.IsAbsoluteUri) |
||||
|
{ |
||||
|
errors.Add(new ValidationError("Url must be specified and absolute.", nameof(action.Url))); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult<IEnumerable<ValidationError>>(errors); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleTriggerValidator.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Read.Schemas.Services; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules.Guards |
||||
|
{ |
||||
|
public sealed class RuleTriggerValidator : IRuleTriggerVisitor<Task<IEnumerable<ValidationError>>> |
||||
|
{ |
||||
|
public ISchemaProvider Schemas { get; } |
||||
|
|
||||
|
public RuleTriggerValidator(ISchemaProvider schemas) |
||||
|
{ |
||||
|
Schemas = schemas; |
||||
|
} |
||||
|
|
||||
|
public static Task<IEnumerable<ValidationError>> ValidateAsync(RuleTrigger action, ISchemaProvider schemas) |
||||
|
{ |
||||
|
Guard.NotNull(action, nameof(action)); |
||||
|
Guard.NotNull(schemas, nameof(schemas)); |
||||
|
|
||||
|
var visitor = new RuleTriggerValidator(schemas); |
||||
|
|
||||
|
return action.Accept(visitor); |
||||
|
} |
||||
|
|
||||
|
public async Task<IEnumerable<ValidationError>> Visit(ContentChangedTrigger trigger) |
||||
|
{ |
||||
|
if (trigger.Schemas != null) |
||||
|
{ |
||||
|
var schemaErrors = await Task.WhenAll( |
||||
|
trigger.Schemas.Select(async s => |
||||
|
await Schemas.FindSchemaByIdAsync(s.SchemaId) == null |
||||
|
? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(trigger.Schemas)) |
||||
|
: null)); |
||||
|
|
||||
|
return schemaErrors.Where(x => x != null).ToList(); |
||||
|
} |
||||
|
|
||||
|
return new List<ValidationError>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleCommandMiddleware.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Read.Schemas.Services; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Guards; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules |
||||
|
{ |
||||
|
public class RuleCommandMiddleware : ICommandMiddleware |
||||
|
{ |
||||
|
private readonly IAggregateHandler handler; |
||||
|
private readonly ISchemaProvider schemas; |
||||
|
|
||||
|
public RuleCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) |
||||
|
{ |
||||
|
Guard.NotNull(handler, nameof(handler)); |
||||
|
Guard.NotNull(schemas, nameof(schemas)); |
||||
|
|
||||
|
this.handler = handler; |
||||
|
this.schemas = schemas; |
||||
|
} |
||||
|
|
||||
|
protected Task On(CreateRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.CreateAsync<RuleDomainObject>(context, async w => |
||||
|
{ |
||||
|
await GuardRule.CanCreate(command, schemas); |
||||
|
|
||||
|
w.Create(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(UpdateRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, async c => |
||||
|
{ |
||||
|
await GuardRule.CanUpdate(command, schemas); |
||||
|
|
||||
|
c.Update(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(EnableRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, r => |
||||
|
{ |
||||
|
GuardRule.CanEnable(command, r.Rule); |
||||
|
|
||||
|
r.Enable(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(DisableRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, r => |
||||
|
{ |
||||
|
GuardRule.CanDisable(command, r.Rule); |
||||
|
|
||||
|
r.Disable(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(DeleteRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, c => |
||||
|
{ |
||||
|
GuardRule.CanDelete(command); |
||||
|
|
||||
|
c.Delete(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
if (!await this.DispatchActionAsync(context.Command, context)) |
||||
|
{ |
||||
|
await next(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDomainObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Events.Rules; |
||||
|
using Squidex.Domain.Apps.Events.Rules.Utils; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules |
||||
|
{ |
||||
|
public class RuleDomainObject : DomainObjectBase |
||||
|
{ |
||||
|
private Rule rule; |
||||
|
private bool isDeleted; |
||||
|
|
||||
|
public Rule Rule |
||||
|
{ |
||||
|
get { return rule; } |
||||
|
} |
||||
|
|
||||
|
public RuleDomainObject(Guid id, int version) |
||||
|
: base(id, version) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected void On(RuleCreated @event) |
||||
|
{ |
||||
|
rule = RuleEventDispatcher.Create(@event); |
||||
|
} |
||||
|
|
||||
|
protected void On(RuleUpdated @event) |
||||
|
{ |
||||
|
rule.Apply(@event); |
||||
|
} |
||||
|
|
||||
|
protected void On(RuleEnabled @event) |
||||
|
{ |
||||
|
rule.Apply(@event); |
||||
|
} |
||||
|
|
||||
|
protected void On(RuleDisabled @event) |
||||
|
{ |
||||
|
rule.Apply(@event); |
||||
|
} |
||||
|
|
||||
|
protected void On(RuleDeleted @event) |
||||
|
{ |
||||
|
isDeleted = true; |
||||
|
} |
||||
|
|
||||
|
public void Create(CreateRule command) |
||||
|
{ |
||||
|
VerifyNotCreated(); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleCreated())); |
||||
|
} |
||||
|
|
||||
|
public void Update(UpdateRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleUpdated())); |
||||
|
} |
||||
|
|
||||
|
public void Enable(EnableRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleEnabled())); |
||||
|
} |
||||
|
|
||||
|
public void Disable(DisableRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleDisabled())); |
||||
|
} |
||||
|
|
||||
|
public void Delete(DeleteRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); |
||||
|
} |
||||
|
|
||||
|
private void VerifyNotCreated() |
||||
|
{ |
||||
|
if (rule != null) |
||||
|
{ |
||||
|
throw new DomainException("Webhook has already been created."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void VerifyCreatedAndNotDeleted() |
||||
|
{ |
||||
|
if (isDeleted || rule == null) |
||||
|
{ |
||||
|
throw new DomainException("Webhook has already been deleted or not created yet."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void DispatchEvent(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
this.DispatchAction(@event.Payload); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,23 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// CreateWebhook.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks.Commands |
|
||||
{ |
|
||||
public sealed class CreateWebhook : WebhookEditCommand |
|
||||
{ |
|
||||
public string SharedSecret { get; } = RandomHash.New(); |
|
||||
|
|
||||
public CreateWebhook() |
|
||||
{ |
|
||||
WebhookId = Guid.NewGuid(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookEditCommand.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks.Commands |
|
||||
{ |
|
||||
public abstract class WebhookEditCommand : WebhookAggregateCommand |
|
||||
{ |
|
||||
public Uri Url { get; set; } |
|
||||
|
|
||||
public List<WebhookSchema> Schemas { get; set; } = new List<WebhookSchema>(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,61 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// GuardWebhook.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks.Guards |
|
||||
{ |
|
||||
public static class GuardWebhook |
|
||||
{ |
|
||||
public static Task CanCreate(CreateWebhook command, ISchemaProvider schemas) |
|
||||
{ |
|
||||
Guard.NotNull(command, nameof(command)); |
|
||||
|
|
||||
return Validate.It(() => "Cannot create webhook.", error => ValidateCommandAsync(command, error, schemas)); |
|
||||
} |
|
||||
|
|
||||
public static Task CanUpdate(UpdateWebhook command, ISchemaProvider schemas) |
|
||||
{ |
|
||||
Guard.NotNull(command, nameof(command)); |
|
||||
|
|
||||
return Validate.It(() => "Cannot update webhook.", error => ValidateCommandAsync(command, error, schemas)); |
|
||||
} |
|
||||
|
|
||||
public static void CanDelete(DeleteWebhook command) |
|
||||
{ |
|
||||
Guard.NotNull(command, nameof(command)); |
|
||||
} |
|
||||
|
|
||||
private static async Task ValidateCommandAsync(WebhookEditCommand command, Action<ValidationError> error, ISchemaProvider schemas) |
|
||||
{ |
|
||||
if (command.Url == null || !command.Url.IsAbsoluteUri) |
|
||||
{ |
|
||||
error(new ValidationError("Url must be specified and absolute.", nameof(command.Url))); |
|
||||
} |
|
||||
|
|
||||
if (command.Schemas != null) |
|
||||
{ |
|
||||
var schemaErrors = await Task.WhenAll( |
|
||||
command.Schemas.Select(async s => |
|
||||
await schemas.FindSchemaByIdAsync(s.SchemaId) == null |
|
||||
? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(command.Schemas)) |
|
||||
: null)); |
|
||||
|
|
||||
foreach (var schemaError in schemaErrors.Where(x => x != null)) |
|
||||
{ |
|
||||
error(schemaError); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookCommandMiddleware.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Guards; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS.Commands; |
|
||||
using Squidex.Infrastructure.Dispatching; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks |
|
||||
{ |
|
||||
public class WebhookCommandMiddleware : ICommandMiddleware |
|
||||
{ |
|
||||
private readonly IAggregateHandler handler; |
|
||||
private readonly ISchemaProvider schemas; |
|
||||
|
|
||||
public WebhookCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) |
|
||||
{ |
|
||||
Guard.NotNull(handler, nameof(handler)); |
|
||||
Guard.NotNull(schemas, nameof(schemas)); |
|
||||
|
|
||||
this.handler = handler; |
|
||||
this.schemas = schemas; |
|
||||
} |
|
||||
|
|
||||
protected async Task On(CreateWebhook command, CommandContext context) |
|
||||
{ |
|
||||
await handler.CreateAsync<WebhookDomainObject>(context, async w => |
|
||||
{ |
|
||||
await GuardWebhook.CanCreate(command, schemas); |
|
||||
|
|
||||
w.Create(command); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
protected async Task On(UpdateWebhook command, CommandContext context) |
|
||||
{ |
|
||||
await handler.UpdateAsync<WebhookDomainObject>(context, async c => |
|
||||
{ |
|
||||
await GuardWebhook.CanUpdate(command, schemas); |
|
||||
|
|
||||
c.Update(command); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
protected Task On(DeleteWebhook command, CommandContext context) |
|
||||
{ |
|
||||
return handler.UpdateAsync<WebhookDomainObject>(context, c => |
|
||||
{ |
|
||||
GuardWebhook.CanDelete(command); |
|
||||
|
|
||||
c.Delete(command); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|
||||
{ |
|
||||
if (!await this.DispatchActionAsync(context.Command, context)) |
|
||||
{ |
|
||||
await next(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,82 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookDomainObject.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Squidex.Domain.Apps.Events.Webhooks; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
using Squidex.Infrastructure.Dispatching; |
|
||||
using Squidex.Infrastructure.Reflection; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks |
|
||||
{ |
|
||||
public class WebhookDomainObject : DomainObjectBase |
|
||||
{ |
|
||||
private bool isDeleted; |
|
||||
private bool isCreated; |
|
||||
|
|
||||
public WebhookDomainObject(Guid id, int version) |
|
||||
: base(id, version) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected void On(WebhookCreated @event) |
|
||||
{ |
|
||||
isCreated = true; |
|
||||
} |
|
||||
|
|
||||
protected void On(WebhookDeleted @event) |
|
||||
{ |
|
||||
isDeleted = true; |
|
||||
} |
|
||||
|
|
||||
public void Create(CreateWebhook command) |
|
||||
{ |
|
||||
VerifyNotCreated(); |
|
||||
|
|
||||
RaiseEvent(SimpleMapper.Map(command, new WebhookCreated())); |
|
||||
} |
|
||||
|
|
||||
public void Update(UpdateWebhook command) |
|
||||
{ |
|
||||
VerifyCreatedAndNotDeleted(); |
|
||||
|
|
||||
RaiseEvent(SimpleMapper.Map(command, new WebhookUpdated())); |
|
||||
} |
|
||||
|
|
||||
public void Delete(DeleteWebhook command) |
|
||||
{ |
|
||||
VerifyCreatedAndNotDeleted(); |
|
||||
|
|
||||
RaiseEvent(SimpleMapper.Map(command, new WebhookDeleted())); |
|
||||
} |
|
||||
|
|
||||
private void VerifyNotCreated() |
|
||||
{ |
|
||||
if (isCreated) |
|
||||
{ |
|
||||
throw new DomainException("Webhook has already been created."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void VerifyCreatedAndNotDeleted() |
|
||||
{ |
|
||||
if (isDeleted || !isCreated) |
|
||||
{ |
|
||||
throw new DomainException("Webhook has already been deleted or not created yet."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected override void DispatchEvent(Envelope<IEvent> @event) |
|
||||
{ |
|
||||
this.DispatchAction(@event.Payload); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,48 @@ |
|||||
|
// ==========================================================================
|
||||
|
// WebhookActionTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules.Guards.Actions |
||||
|
{ |
||||
|
public sealed class WebhookActionTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_url_is_null() |
||||
|
{ |
||||
|
var action = new WebhookAction { Url = null }; |
||||
|
|
||||
|
var errors = await RuleActionValidator.ValidateAsync(action); |
||||
|
|
||||
|
Assert.NotEmpty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_url_is_relative() |
||||
|
{ |
||||
|
var action = new WebhookAction { Url = new Uri("/invalid", UriKind.Relative) }; |
||||
|
|
||||
|
var errors = await RuleActionValidator.ValidateAsync(action); |
||||
|
|
||||
|
Assert.NotEmpty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_url_is_absolute() |
||||
|
{ |
||||
|
var action = new WebhookAction { Url = new Uri("https://squidex.io", UriKind.Absolute) }; |
||||
|
|
||||
|
var errors = await RuleActionValidator.ValidateAsync(action); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,158 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardRuleTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Read.Schemas; |
||||
|
using Squidex.Domain.Apps.Read.Schemas.Services; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules.Guards |
||||
|
{ |
||||
|
public class GuardRuleTests |
||||
|
{ |
||||
|
private readonly Uri validUrl = new Uri("https://squidex.io"); |
||||
|
private readonly Rule rule = new Rule(new ContentChangedTrigger(), new WebhookAction()); |
||||
|
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>(); |
||||
|
|
||||
|
public GuardRuleTests() |
||||
|
{ |
||||
|
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false)) |
||||
|
.Returns(A.Fake<ISchemaEntity>()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CanCreate_should_throw_exception_if_trigger_null() |
||||
|
{ |
||||
|
var command = new CreateRule |
||||
|
{ |
||||
|
Trigger = null, |
||||
|
Action = new WebhookAction |
||||
|
{ |
||||
|
Url = validUrl |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
await Assert.ThrowsAsync<ValidationException>(() => GuardRule.CanCreate(command, schemas)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CanCreate_should_throw_exception_if_action_null() |
||||
|
{ |
||||
|
var command = new CreateRule |
||||
|
{ |
||||
|
Trigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = new List<ContentChangedTriggerSchema>() |
||||
|
}, |
||||
|
Action = null |
||||
|
}; |
||||
|
|
||||
|
await Assert.ThrowsAsync<ValidationException>(() => GuardRule.CanCreate(command, schemas)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CanCreate_should_not_throw_exception_if_trigger_and_action_valid() |
||||
|
{ |
||||
|
var command = new CreateRule |
||||
|
{ |
||||
|
Trigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = new List<ContentChangedTriggerSchema>() |
||||
|
}, |
||||
|
Action = new WebhookAction |
||||
|
{ |
||||
|
Url = validUrl |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
await GuardRule.CanCreate(command, schemas); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CanUpdate_should_throw_exception_if_action_and_trigger_are_null() |
||||
|
{ |
||||
|
var command = new UpdateRule(); |
||||
|
|
||||
|
await Assert.ThrowsAsync<ValidationException>(() => GuardRule.CanUpdate(command, schemas)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CanUpdate_should_not_throw_exception_if_trigger_and_action_valid() |
||||
|
{ |
||||
|
var command = new UpdateRule |
||||
|
{ |
||||
|
Trigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = new List<ContentChangedTriggerSchema>() |
||||
|
}, |
||||
|
Action = new WebhookAction |
||||
|
{ |
||||
|
Url = validUrl |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
await GuardRule.CanUpdate(command, schemas); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanEnable_should_throw_exception_if_rule_enabled() |
||||
|
{ |
||||
|
var command = new EnableRule(); |
||||
|
|
||||
|
rule.Enable(); |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardRule.CanEnable(command, rule)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanEnable_should_not_throw_exception_if_rule_disabled() |
||||
|
{ |
||||
|
var command = new EnableRule(); |
||||
|
|
||||
|
rule.Disable(); |
||||
|
|
||||
|
GuardRule.CanEnable(command, rule); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanDisable_should_throw_exception_if_rule_disabled() |
||||
|
{ |
||||
|
var command = new DisableRule(); |
||||
|
|
||||
|
rule.Disable(); |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardRule.CanDisable(command, rule)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanDisable_should_not_throw_exception_if_rule_enabled() |
||||
|
{ |
||||
|
var command = new DisableRule(); |
||||
|
|
||||
|
rule.Enable(); |
||||
|
|
||||
|
GuardRule.CanDisable(command, rule); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanDelete_should_not_throw_exception() |
||||
|
{ |
||||
|
var command = new DeleteRule(); |
||||
|
|
||||
|
GuardRule.CanDelete(command); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentChangedTriggerTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Read.Schemas; |
||||
|
using Squidex.Domain.Apps.Read.Schemas.Services; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers |
||||
|
{ |
||||
|
public class ContentChangedTriggerTests |
||||
|
{ |
||||
|
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>(); |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_schemas_ids_are_not_valid() |
||||
|
{ |
||||
|
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false)) |
||||
|
.Returns(Task.FromResult<ISchemaEntity>(null)); |
||||
|
|
||||
|
var trigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = new List<ContentChangedTriggerSchema> |
||||
|
{ |
||||
|
new ContentChangedTriggerSchema() |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); |
||||
|
|
||||
|
Assert.NotEmpty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_schemas_is_null() |
||||
|
{ |
||||
|
var trigger = new ContentChangedTrigger(); |
||||
|
|
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_schemas_is_empty() |
||||
|
{ |
||||
|
var trigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = new List<ContentChangedTriggerSchema>() |
||||
|
}; |
||||
|
|
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_schemas_ids_are_valid() |
||||
|
{ |
||||
|
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false)) |
||||
|
.Returns(A.Fake<ISchemaEntity>()); |
||||
|
|
||||
|
var trigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = new List<ContentChangedTriggerSchema> |
||||
|
{ |
||||
|
new ContentChangedTriggerSchema() |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleCommandMiddlewareTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Read.Schemas; |
||||
|
using Squidex.Domain.Apps.Read.Schemas.Services; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Domain.Apps.Write.TestHelpers; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules |
||||
|
{ |
||||
|
public class RuleCommandMiddlewareTests : HandlerTestBase<RuleDomainObject> |
||||
|
{ |
||||
|
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>(); |
||||
|
private readonly RuleCommandMiddleware sut; |
||||
|
private readonly RuleDomainObject rule; |
||||
|
private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); |
||||
|
private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; |
||||
|
private readonly Guid ruleId = Guid.NewGuid(); |
||||
|
|
||||
|
public RuleCommandMiddlewareTests() |
||||
|
{ |
||||
|
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false)) |
||||
|
.Returns(A.Fake<ISchemaEntity>()); |
||||
|
|
||||
|
rule = new RuleDomainObject(ruleId, -1); |
||||
|
|
||||
|
sut = new RuleCommandMiddleware(Handler, schemas); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Create_should_create_domain_object() |
||||
|
{ |
||||
|
var context = CreateContextForCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); |
||||
|
|
||||
|
await TestCreate(rule, async _ => |
||||
|
{ |
||||
|
await sut.HandleAsync(context); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Update_should_update_domain_object() |
||||
|
{ |
||||
|
var context = CreateContextForCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction }); |
||||
|
|
||||
|
CreateRule(); |
||||
|
|
||||
|
await TestUpdate(rule, async _ => |
||||
|
{ |
||||
|
await sut.HandleAsync(context); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Enable_should_update_domain_object() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
DisableRule(); |
||||
|
|
||||
|
var command = CreateContextForCommand(new EnableRule { RuleId = ruleId }); |
||||
|
|
||||
|
await TestUpdate(rule, async _ => |
||||
|
{ |
||||
|
await sut.HandleAsync(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Disable_should_update_domain_object() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
|
||||
|
var command = CreateContextForCommand(new DisableRule { RuleId = ruleId }); |
||||
|
|
||||
|
await TestUpdate(rule, async _ => |
||||
|
{ |
||||
|
await sut.HandleAsync(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Delete_should_update_domain_object() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
|
||||
|
var command = CreateContextForCommand(new DeleteRule { RuleId = ruleId }); |
||||
|
|
||||
|
await TestUpdate(rule, async _ => |
||||
|
{ |
||||
|
await sut.HandleAsync(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void DisableRule() |
||||
|
{ |
||||
|
rule.Disable(new DisableRule()); |
||||
|
} |
||||
|
|
||||
|
private void CreateRule() |
||||
|
{ |
||||
|
rule.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,240 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDomainObjectTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Events.Rules; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Domain.Apps.Write.TestHelpers; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Rules |
||||
|
{ |
||||
|
public class RuleDomainObjectTests : HandlerTestBase<RuleDomainObject> |
||||
|
{ |
||||
|
private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); |
||||
|
private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; |
||||
|
private readonly RuleDomainObject sut; |
||||
|
|
||||
|
public Guid RuleId { get; } = Guid.NewGuid(); |
||||
|
|
||||
|
public RuleDomainObjectTests() |
||||
|
{ |
||||
|
sut = new RuleDomainObject(RuleId, 0); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Create_should_throw_exception_if_created() |
||||
|
{ |
||||
|
sut.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); |
||||
|
|
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Create_should_create_events() |
||||
|
{ |
||||
|
var command = new CreateRule { Trigger = ruleTrigger, Action = ruleAction }; |
||||
|
|
||||
|
sut.Create(CreateRuleCommand(command)); |
||||
|
|
||||
|
sut.GetUncomittedEvents() |
||||
|
.ShouldHaveSameEvents( |
||||
|
CreateRuleEvent(new RuleCreated { Trigger = ruleTrigger, Action = ruleAction }) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Update_should_throw_exception_if_not_created() |
||||
|
{ |
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Update(CreateRuleCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction })); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Update_should_throw_exception_if_rule_is_deleted() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
DeleteRule(); |
||||
|
|
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Update(CreateRuleCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction })); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Update_should_create_events() |
||||
|
{ |
||||
|
var newTrigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = new List<ContentChangedTriggerSchema>() |
||||
|
}; |
||||
|
|
||||
|
var newAction = new WebhookAction |
||||
|
{ |
||||
|
Url = new Uri("https://squidex.io/v2") |
||||
|
}; |
||||
|
|
||||
|
CreateRule(); |
||||
|
|
||||
|
var command = new UpdateRule { Trigger = newTrigger, Action = newAction }; |
||||
|
|
||||
|
sut.Update(CreateRuleCommand(command)); |
||||
|
|
||||
|
sut.GetUncomittedEvents() |
||||
|
.ShouldHaveSameEvents( |
||||
|
CreateRuleEvent(new RuleUpdated { Trigger = newTrigger, Action = newAction }) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Enable_should_throw_exception_if_not_created() |
||||
|
{ |
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Enable(CreateRuleCommand(new EnableRule())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Enable_should_throw_exception_if_rule_is_deleted() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
DeleteRule(); |
||||
|
|
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Enable(CreateRuleCommand(new EnableRule())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Enable_should_create_events() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
|
||||
|
var command = new EnableRule(); |
||||
|
|
||||
|
sut.Enable(CreateRuleCommand(command)); |
||||
|
|
||||
|
sut.GetUncomittedEvents() |
||||
|
.ShouldHaveSameEvents( |
||||
|
CreateRuleEvent(new RuleEnabled()) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Disable_should_throw_exception_if_not_created() |
||||
|
{ |
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Disable(CreateRuleCommand(new DisableRule())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Disable_should_throw_exception_if_rule_is_deleted() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
DeleteRule(); |
||||
|
|
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Disable(CreateRuleCommand(new DisableRule())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Disable_should_create_events() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
|
||||
|
var command = new DisableRule(); |
||||
|
|
||||
|
sut.Disable(CreateRuleCommand(command)); |
||||
|
|
||||
|
sut.GetUncomittedEvents() |
||||
|
.ShouldHaveSameEvents( |
||||
|
CreateRuleEvent(new RuleDisabled()) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Delete_should_throw_exception_if_not_created() |
||||
|
{ |
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Delete(CreateRuleCommand(new DeleteRule())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Delete_should_throw_exception_if_already_deleted() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
DeleteRule(); |
||||
|
|
||||
|
Assert.Throws<DomainException>(() => |
||||
|
{ |
||||
|
sut.Delete(CreateRuleCommand(new DeleteRule())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Delete_should_update_create_events() |
||||
|
{ |
||||
|
CreateRule(); |
||||
|
|
||||
|
sut.Delete(CreateRuleCommand(new DeleteRule())); |
||||
|
|
||||
|
sut.GetUncomittedEvents() |
||||
|
.ShouldHaveSameEvents( |
||||
|
CreateRuleEvent(new RuleDeleted()) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private void CreateRule() |
||||
|
{ |
||||
|
sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); |
||||
|
|
||||
|
((IAggregate)sut).ClearUncommittedEvents(); |
||||
|
} |
||||
|
|
||||
|
private void DeleteRule() |
||||
|
{ |
||||
|
sut.Delete(CreateRuleCommand(new DeleteRule())); |
||||
|
|
||||
|
((IAggregate)sut).ClearUncommittedEvents(); |
||||
|
} |
||||
|
|
||||
|
protected T CreateRuleEvent<T>(T @event) where T : RuleEvent |
||||
|
{ |
||||
|
@event.RuleId = RuleId; |
||||
|
|
||||
|
return CreateEvent(@event); |
||||
|
} |
||||
|
|
||||
|
protected T CreateRuleCommand<T>(T command) where T : RuleAggregateCommand |
||||
|
{ |
||||
|
command.RuleId = RuleId; |
||||
|
|
||||
|
return CreateCommand(command); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,138 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// GuardWebhookTests.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
using FakeItEasy; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
using Squidex.Domain.Apps.Read.Schemas; |
|
||||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks.Guards |
|
||||
{ |
|
||||
public class GuardWebhookTests |
|
||||
{ |
|
||||
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>(); |
|
||||
|
|
||||
public GuardWebhookTests() |
|
||||
{ |
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false)) |
|
||||
.Returns(A.Fake<ISchemaEntity>()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanCreate_should_throw_exception_if_url_defined() |
|
||||
{ |
|
||||
var command = new CreateWebhook(); |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanCreate_should_throw_exception_if_url_not_valid() |
|
||||
{ |
|
||||
var command = new CreateWebhook { Url = new Uri("/invalid", UriKind.Relative) }; |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanCreate_should_throw_exception_if_schema_id_not_found() |
|
||||
{ |
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false)) |
|
||||
.Returns(Task.FromResult<ISchemaEntity>(null)); |
|
||||
|
|
||||
var command = new CreateWebhook |
|
||||
{ |
|
||||
Schemas = new List<WebhookSchema> |
|
||||
{ |
|
||||
new WebhookSchema() |
|
||||
}, |
|
||||
Url = new Uri("/invalid", UriKind.Relative) |
|
||||
}; |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanCreate_should_not_throw_exception_if_schema_id_found() |
|
||||
{ |
|
||||
var command = new CreateWebhook |
|
||||
{ |
|
||||
Schemas = new List<WebhookSchema> |
|
||||
{ |
|
||||
new WebhookSchema() |
|
||||
}, |
|
||||
Url = new Uri("/invalid", UriKind.Relative) |
|
||||
}; |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanUpdate_should_throw_exception_if_url_not_defined() |
|
||||
{ |
|
||||
var command = new UpdateWebhook(); |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanUpdate_should_throw_exception_if_url_not_valid() |
|
||||
{ |
|
||||
var command = new UpdateWebhook { Url = new Uri("/invalid", UriKind.Relative) }; |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanUpdate_should_throw_exception_if_schema_id_not_found() |
|
||||
{ |
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false)) |
|
||||
.Returns(Task.FromResult<ISchemaEntity>(null)); |
|
||||
|
|
||||
var command = new UpdateWebhook |
|
||||
{ |
|
||||
Schemas = new List<WebhookSchema> |
|
||||
{ |
|
||||
new WebhookSchema() |
|
||||
}, |
|
||||
Url = new Uri("/invalid", UriKind.Relative) |
|
||||
}; |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task CanUpdate_should_not_throw_exception_if_schema_id_found() |
|
||||
{ |
|
||||
var command = new UpdateWebhook |
|
||||
{ |
|
||||
Schemas = new List<WebhookSchema> |
|
||||
{ |
|
||||
new WebhookSchema() |
|
||||
}, |
|
||||
Url = new Uri("/invalid", UriKind.Relative) |
|
||||
}; |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CanDelete_should_not_throw_exception() |
|
||||
{ |
|
||||
var command = new DeleteWebhook(); |
|
||||
|
|
||||
GuardWebhook.CanDelete(command); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,115 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookCommandMiddlewareTests.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
using FakeItEasy; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
using Squidex.Domain.Apps.Read.Schemas; |
|
||||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|
||||
using Squidex.Domain.Apps.Write.TestHelpers; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS.Commands; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks |
|
||||
{ |
|
||||
public class WebhookCommandMiddlewareTests : HandlerTestBase<WebhookDomainObject> |
|
||||
{ |
|
||||
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>(); |
|
||||
private readonly WebhookCommandMiddleware sut; |
|
||||
private readonly WebhookDomainObject webhook; |
|
||||
private readonly Uri url = new Uri("http://squidex.io"); |
|
||||
private readonly Guid schemaId = Guid.NewGuid(); |
|
||||
private readonly Guid webhookId = Guid.NewGuid(); |
|
||||
private readonly List<WebhookSchema> webhookSchemas; |
|
||||
|
|
||||
public WebhookCommandMiddlewareTests() |
|
||||
{ |
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)) |
|
||||
.Returns(A.Fake<ISchemaEntity>()); |
|
||||
|
|
||||
webhook = new WebhookDomainObject(webhookId, -1); |
|
||||
|
|
||||
webhookSchemas = new List<WebhookSchema> |
|
||||
{ |
|
||||
new WebhookSchema { SchemaId = schemaId } |
|
||||
}; |
|
||||
|
|
||||
sut = new WebhookCommandMiddleware(Handler, schemas); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Create_should_create_domain_object() |
|
||||
{ |
|
||||
var context = CreateContextForCommand(new CreateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId }); |
|
||||
|
|
||||
await TestCreate(webhook, async _ => |
|
||||
{ |
|
||||
await sut.HandleAsync(context); |
|
||||
}); |
|
||||
|
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).MustHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Update_should_update_domain_object() |
|
||||
{ |
|
||||
var context = CreateContextForCommand(new UpdateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId }); |
|
||||
|
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).Returns(A.Fake<ISchemaEntity>()); |
|
||||
|
|
||||
CreateWebhook(); |
|
||||
|
|
||||
await TestUpdate(webhook, async _ => |
|
||||
{ |
|
||||
await sut.HandleAsync(context); |
|
||||
}); |
|
||||
|
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).MustHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Update_should_throw_exception_when_schema_is_not_found() |
|
||||
{ |
|
||||
var context = CreateContextForCommand(new UpdateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId }); |
|
||||
|
|
||||
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).Returns((ISchemaEntity)null); |
|
||||
|
|
||||
CreateWebhook(); |
|
||||
|
|
||||
await Assert.ThrowsAsync<ValidationException>(async () => |
|
||||
{ |
|
||||
await TestCreate(webhook, async _ => |
|
||||
{ |
|
||||
await sut.HandleAsync(context); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Delete_should_update_domain_object() |
|
||||
{ |
|
||||
CreateWebhook(); |
|
||||
|
|
||||
var command = CreateContextForCommand(new DeleteWebhook { WebhookId = webhookId }); |
|
||||
|
|
||||
await TestUpdate(webhook, async _ => |
|
||||
{ |
|
||||
await sut.HandleAsync(command); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void CreateWebhook() |
|
||||
{ |
|
||||
webhook.Create(new CreateWebhook { Url = url }); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,159 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookDomainObjectTests.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Squidex.Domain.Apps.Events.Webhooks; |
|
||||
using Squidex.Domain.Apps.Write.TestHelpers; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Write.Webhooks |
|
||||
{ |
|
||||
public class WebhookDomainObjectTests : HandlerTestBase<WebhookDomainObject> |
|
||||
{ |
|
||||
private readonly Uri url = new Uri("http://squidex.io"); |
|
||||
private readonly WebhookDomainObject sut; |
|
||||
|
|
||||
public Guid WebhookId { get; } = Guid.NewGuid(); |
|
||||
|
|
||||
public WebhookDomainObjectTests() |
|
||||
{ |
|
||||
sut = new WebhookDomainObject(WebhookId, 0); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Create_should_throw_exception_if_created() |
|
||||
{ |
|
||||
sut.Create(new CreateWebhook { Url = url }); |
|
||||
|
|
||||
Assert.Throws<DomainException>(() => |
|
||||
{ |
|
||||
sut.Create(CreateWebhookCommand(new CreateWebhook { Url = url })); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Create_should_create_events() |
|
||||
{ |
|
||||
var command = new CreateWebhook { Url = url }; |
|
||||
|
|
||||
sut.Create(CreateWebhookCommand(command)); |
|
||||
|
|
||||
sut.GetUncomittedEvents() |
|
||||
.ShouldHaveSameEvents( |
|
||||
CreateWebhookEvent(new WebhookCreated |
|
||||
{ |
|
||||
Url = url, |
|
||||
Schemas = command.Schemas, |
|
||||
SharedSecret = command.SharedSecret, |
|
||||
WebhookId = command.WebhookId |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Update_should_throw_exception_if_not_created() |
|
||||
{ |
|
||||
Assert.Throws<DomainException>(() => |
|
||||
{ |
|
||||
sut.Update(CreateWebhookCommand(new UpdateWebhook { Url = url })); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Update_should_throw_exception_if_webhook_is_deleted() |
|
||||
{ |
|
||||
CreateWebhook(); |
|
||||
DeleteWebhook(); |
|
||||
|
|
||||
Assert.Throws<DomainException>(() => |
|
||||
{ |
|
||||
sut.Update(CreateWebhookCommand(new UpdateWebhook { Url = url })); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Update_should_create_events() |
|
||||
{ |
|
||||
CreateWebhook(); |
|
||||
|
|
||||
var command = new UpdateWebhook { Url = url }; |
|
||||
|
|
||||
sut.Update(CreateWebhookCommand(command)); |
|
||||
|
|
||||
sut.GetUncomittedEvents() |
|
||||
.ShouldHaveSameEvents( |
|
||||
CreateWebhookEvent(new WebhookUpdated { Url = url, Schemas = command.Schemas }) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Delete_should_throw_exception_if_not_created() |
|
||||
{ |
|
||||
Assert.Throws<DomainException>(() => |
|
||||
{ |
|
||||
sut.Delete(CreateWebhookCommand(new DeleteWebhook())); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Delete_should_throw_exception_if_already_deleted() |
|
||||
{ |
|
||||
CreateWebhook(); |
|
||||
DeleteWebhook(); |
|
||||
|
|
||||
Assert.Throws<DomainException>(() => |
|
||||
{ |
|
||||
sut.Delete(CreateWebhookCommand(new DeleteWebhook())); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Delete_should_update_properties_create_events() |
|
||||
{ |
|
||||
CreateWebhook(); |
|
||||
|
|
||||
sut.Delete(CreateWebhookCommand(new DeleteWebhook())); |
|
||||
|
|
||||
sut.GetUncomittedEvents() |
|
||||
.ShouldHaveSameEvents( |
|
||||
CreateWebhookEvent(new WebhookDeleted()) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
private void CreateWebhook() |
|
||||
{ |
|
||||
sut.Create(CreateWebhookCommand(new CreateWebhook { Url = url })); |
|
||||
|
|
||||
((IAggregate)sut).ClearUncommittedEvents(); |
|
||||
} |
|
||||
|
|
||||
private void DeleteWebhook() |
|
||||
{ |
|
||||
sut.Delete(CreateWebhookCommand(new DeleteWebhook())); |
|
||||
|
|
||||
((IAggregate)sut).ClearUncommittedEvents(); |
|
||||
} |
|
||||
|
|
||||
protected T CreateWebhookEvent<T>(T @event) where T : WebhookEvent |
|
||||
{ |
|
||||
@event.WebhookId = WebhookId; |
|
||||
|
|
||||
return CreateEvent(@event); |
|
||||
} |
|
||||
|
|
||||
protected T CreateWebhookCommand<T>(T command) where T : WebhookAggregateCommand |
|
||||
{ |
|
||||
command.WebhookId = WebhookId; |
|
||||
|
|
||||
return CreateCommand(command); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue