Browse Source

Webhook Read Model and Invoker

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
e79d73afb0
  1. 19
      src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs
  2. 12
      src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs
  3. 4
      src/Squidex.Read.MongoDb/Apps/MongoAppEntityClient.cs
  4. 4
      src/Squidex.Read.MongoDb/Apps/MongoAppEntityContributor.cs
  5. 4
      src/Squidex.Read.MongoDb/Apps/MongoAppEntityLanguage.cs
  6. 2
      src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  7. 10
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs
  8. 29
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntityWebhook.cs
  9. 10
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
  10. 35
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaWebhookEntity.cs
  11. 121
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaWebhookRepository.cs
  12. 2
      src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs
  13. 3
      src/Squidex.Read/Schemas/ISchemaEntity.cs
  14. 21
      src/Squidex.Read/Schemas/ISchemaWebhookEntity.cs
  15. 19
      src/Squidex.Read/Schemas/Repositories/ISchemaWebhookRepository.cs
  16. 10
      src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
  17. 119
      src/Squidex.Read/Schemas/WebhookInvoker.cs
  18. 4
      src/Squidex/Config/Domain/ReadModule.cs
  19. 16
      src/Squidex/Config/Domain/StoreMongoDbModule.cs
  20. 4
      src/Squidex/Config/Domain/WriteModule.cs
  21. 28
      tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs
  22. 1
      tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs

19
src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs

@ -17,29 +17,24 @@ namespace Squidex.Infrastructure.CQRS.Events
public string Name { get; }
public string EventsFilter
{
get { return inners.FirstOrDefault()?.EventsFilter; }
}
public string EventsFilter { get; }
public CompoundEventConsumer(IEventConsumer first, params IEventConsumer[] inners)
: this(first?.Name, first, inners)
{
Guard.NotNull(first, nameof(first));
Guard.NotNull(inners, nameof(inners));
this.inners = new[] { first }.Union(inners).ToArray();
Name = first.Name;
}
public CompoundEventConsumer(string name, params IEventConsumer[] inners)
public CompoundEventConsumer(string name, IEventConsumer first, params IEventConsumer[] inners)
{
Guard.NotNull(first, nameof(first));
Guard.NotNull(inners, nameof(inners));
Guard.NotNullOrEmpty(name, nameof(name));
this.inners = inners;
this.inners = new[] { first }.Union(inners).ToArray();
Name = name;
EventsFilter = string.Join("|", this.inners.Where(x => !string.IsNullOrWhiteSpace(x.EventsFilter)).Select(x => $"({x.EventsFilter})"));
}
public Task ClearAsync()

12
src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs

@ -46,15 +46,15 @@ namespace Squidex.Read.MongoDb.Apps
[BsonRequired]
[BsonElement]
public List<MongoAppLanguage> Languages { get; set; } = new List<MongoAppLanguage>();
public List<MongoAppEntityLanguage> Languages { get; set; } = new List<MongoAppEntityLanguage>();
[BsonRequired]
[BsonElement]
public Dictionary<string, MongoAppClientEntity> Clients { get; set; } = new Dictionary<string, MongoAppClientEntity>();
public Dictionary<string, MongoAppEntityClient> Clients { get; set; } = new Dictionary<string, MongoAppEntityClient>();
[BsonRequired]
[BsonElement]
public Dictionary<string, MongoAppContributorEntity> Contributors { get; set; } = new Dictionary<string, MongoAppContributorEntity>();
public Dictionary<string, MongoAppEntityContributor> Contributors { get; set; } = new Dictionary<string, MongoAppEntityContributor>();
public PartitionResolver PartitionResolver
{
@ -101,12 +101,12 @@ namespace Squidex.Read.MongoDb.Apps
return languagesConfig;
}
private static MongoAppLanguage FromLanguageConfig(LanguageConfig l)
private static MongoAppEntityLanguage FromLanguageConfig(LanguageConfig l)
{
return new MongoAppLanguage { Iso2Code = l.Language, IsOptional = l.IsOptional, Fallback = l.Fallback.Select(x => x.Iso2Code).ToList() };
return new MongoAppEntityLanguage { Iso2Code = l.Language, IsOptional = l.IsOptional, Fallback = l.Fallback.Select(x => x.Iso2Code).ToList() };
}
private static LanguageConfig ToLanguageConfig(MongoAppLanguage l)
private static LanguageConfig ToLanguageConfig(MongoAppEntityLanguage l)
{
return new LanguageConfig(l.Iso2Code, l.IsOptional, l.Fallback?.Select<string, Language>(f => f));
}

4
src/Squidex.Read.MongoDb/Apps/MongoAppClientEntity.cs → src/Squidex.Read.MongoDb/Apps/MongoAppEntityClient.cs

@ -1,5 +1,5 @@
// ==========================================================================
// MongoAppClientEntity.cs
// MongoAppEntityClient.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -11,7 +11,7 @@ using Squidex.Read.Apps;
namespace Squidex.Read.MongoDb.Apps
{
public sealed class MongoAppClientEntity : IAppClientEntity
public sealed class MongoAppEntityClient : IAppClientEntity
{
[BsonRequired]
[BsonElement]

4
src/Squidex.Read.MongoDb/Apps/MongoAppContributorEntity.cs → src/Squidex.Read.MongoDb/Apps/MongoAppEntityContributor.cs

@ -1,5 +1,5 @@
// ==========================================================================
// MongoAppContributorEntity.cs
// MongoAppEntityContributor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -12,7 +12,7 @@ using Squidex.Read.Apps;
namespace Squidex.Read.MongoDb.Apps
{
public sealed class MongoAppContributorEntity : IAppContributorEntity
public sealed class MongoAppEntityContributor : IAppContributorEntity
{
[BsonRequired]
[BsonElement]

4
src/Squidex.Read.MongoDb/Apps/MongoAppLanguage.cs → src/Squidex.Read.MongoDb/Apps/MongoAppEntityLanguage.cs

@ -1,5 +1,5 @@
// ==========================================================================
// MongoAppLanguage.cs
// MongoAppEntityLanguage.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -11,7 +11,7 @@ using MongoDB.Bson.Serialization.Attributes;
namespace Squidex.Read.MongoDb.Apps
{
public sealed class MongoAppLanguage
public sealed class MongoAppEntityLanguage
{
[BsonRequired]
[BsonElement]

2
src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs

@ -53,7 +53,7 @@ namespace Squidex.Read.MongoDb.Apps
{
return Collection.UpdateAsync(@event, headers, a =>
{
a.Clients[@event.Id] = SimpleMapper.Map(@event, new MongoAppClientEntity());
a.Clients[@event.Id] = SimpleMapper.Map(@event, new MongoAppEntityClient());
});
}

10
src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs

@ -7,6 +7,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
@ -53,6 +54,15 @@ namespace Squidex.Read.MongoDb.Schemas
[BsonElement]
public bool IsDeleted { get; set; }
[BsonRequired]
[BsonElement]
public List<MongoSchemaEntityWebhook> Webhooks { get; set; } = new List<MongoSchemaEntityWebhook>();
IEnumerable<ISchemaWebhookEntity> ISchemaEntity.Webhooks
{
get { return Webhooks; }
}
Schema ISchemaEntity.Schema
{
get { return schema.Value; }

29
src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntityWebhook.cs

@ -0,0 +1,29 @@
// ==========================================================================
// MongoSchemaEntityWebhook.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Read.Schemas;
namespace Squidex.Read.MongoDb.Schemas
{
public sealed class MongoSchemaEntityWebhook : ISchemaWebhookEntity
{
[BsonRequired]
[BsonElement]
public Guid Id { get; set; }
[BsonRequired]
[BsonElement]
public Uri Url { get; set; }
[BsonRequired]
[BsonElement]
public string SecurityToken { get; set; }
}
}

10
src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs

@ -98,6 +98,16 @@ namespace Squidex.Read.MongoDb.Schemas
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s, registry));
}
protected Task On(WebhookAdded @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, e => e.Webhooks.Add(SimpleMapper.Map(@event, new MongoSchemaEntityWebhook())));
}
protected Task On(WebhookDeleted @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, e => e.Webhooks.RemoveAll(w => w.Id == @event.Id));
}
protected Task On(SchemaDeleted @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, e => e.IsDeleted = true);

35
src/Squidex.Read.MongoDb/Schemas/MongoSchemaWebhookEntity.cs

@ -0,0 +1,35 @@
// ==========================================================================
// MongoSchemaWebhookEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Read.Schemas;
namespace Squidex.Read.MongoDb.Schemas
{
public class MongoSchemaWebhookEntity : ISchemaWebhookEntity
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
[BsonRequired]
[BsonElement]
public Uri Url { get; set; }
[BsonRequired]
[BsonElement]
public string SecurityToken { get; set; }
[BsonRequired]
[BsonElement]
public Guid SchemaId { get; set; }
}
}

121
src/Squidex.Read.MongoDb/Schemas/MongoSchemaWebhookRepository.cs

@ -0,0 +1,121 @@
// ==========================================================================
// MongoSchemaWebhookRepository.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.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Repositories;
namespace Squidex.Read.MongoDb.Schemas
{
public class MongoSchemaWebhookRepository : MongoRepositoryBase<MongoSchemaWebhookEntity>, ISchemaWebhookRepository, IEventConsumer
{
private static readonly List<ISchemaWebhookEntity> EmptyWebhooks = new List<ISchemaWebhookEntity>();
private Dictionary<Guid, List<MongoSchemaWebhookEntity>> inMemoryWebhooks;
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1);
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^schema-"; }
}
public MongoSchemaWebhookRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "Projections_SchemaWebhooks";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaWebhookEntity> collection)
{
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SchemaId));
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected async Task On(WebhookAdded @event, EnvelopeHeaders headers)
{
await EnsureWebooksLoadedAsync();
var webhook = SimpleMapper.Map(@event, new MongoSchemaWebhookEntity { SchemaId = @event.SchemaId.Id });
inMemoryWebhooks.GetOrAddNew(webhook.SchemaId).Add(webhook);
await Collection.InsertOneAsync(webhook);
}
protected async Task On(WebhookDeleted @event, EnvelopeHeaders headers)
{
await EnsureWebooksLoadedAsync();
inMemoryWebhooks.GetOrDefault(@event.SchemaId.Id)?.RemoveAll(w => w.Id == @event.Id);
await Collection.DeleteManyAsync(x => x.Id == @event.Id);
}
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers)
{
await EnsureWebooksLoadedAsync();
inMemoryWebhooks.Remove(@event.SchemaId.Id);
await Collection.DeleteManyAsync(x => x.SchemaId == @event.SchemaId.Id);
}
public async Task<IReadOnlyList<ISchemaWebhookEntity>> QueryBySchemaAsync(Guid schemaId)
{
await EnsureWebooksLoadedAsync();
return inMemoryWebhooks.GetOrDefault(schemaId)?.OfType<ISchemaWebhookEntity>()?.ToList() ?? EmptyWebhooks;
}
private async Task EnsureWebooksLoadedAsync()
{
if (inMemoryWebhooks == null)
{
try
{
await lockObject.WaitAsync();
if (inMemoryWebhooks == null)
{
var webhooks = await Collection.Find(new BsonDocument()).ToListAsync();
inMemoryWebhooks = webhooks.GroupBy(x => x.SchemaId).ToDictionary(x => x.Key, x => x.ToList());
}
}
finally
{
lockObject.Release();
}
}
}
}
}

2
src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs

@ -34,7 +34,7 @@ namespace Squidex.Read.Apps.Services.Implementations
public string EventsFilter
{
get { return "*"; }
get { return string.Empty; }
}
public CachingAppProvider(IMemoryCache cache, IAppRepository repository)

3
src/Squidex.Read/Schemas/ISchemaEntity.cs

@ -7,6 +7,7 @@
// ==========================================================================
using Squidex.Core.Schemas;
using System.Collections.Generic;
namespace Squidex.Read.Schemas
{
@ -19,5 +20,7 @@ namespace Squidex.Read.Schemas
bool IsDeleted { get; }
Schema Schema { get; }
IEnumerable<ISchemaWebhookEntity> Webhooks { get; }
}
}

21
src/Squidex.Read/Schemas/ISchemaWebhookEntity.cs

@ -0,0 +1,21 @@
// ==========================================================================
// ISchemaWebhookEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
namespace Squidex.Read.Schemas
{
public interface ISchemaWebhookEntity
{
Guid Id { get; }
Uri Url { get; }
string SecurityToken { get; }
}
}

19
src/Squidex.Read/Schemas/Repositories/ISchemaWebhookRepository.cs

@ -0,0 +1,19 @@
// ==========================================================================
// ISchemaWebhookRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Read.Schemas.Repositories
{
public interface ISchemaWebhookRepository
{
Task<IReadOnlyList<ISchemaWebhookEntity>> QueryBySchemaAsync(Guid schemaId);
}
}

10
src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs

@ -34,7 +34,7 @@ namespace Squidex.Read.Schemas.Services.Implementations
public string EventsFilter
{
get { return "*"; }
get { return string.Empty; }
}
public CachingSchemaProvider(IMemoryCache cache, ISchemaRepository repository)
@ -125,6 +125,14 @@ namespace Squidex.Read.Schemas.Services.Implementations
{
Remove(schemaUpdatedEvent.AppId, schemaUpdatedEvent.SchemaId);
}
else if (@event.Payload is WebhookAdded webhookAddedEvent)
{
Remove(webhookAddedEvent.AppId, webhookAddedEvent.SchemaId);
}
else if (@event.Payload is WebhookDeleted webhookDeletedEvent)
{
Remove(webhookDeletedEvent.AppId, webhookDeletedEvent.SchemaId);
}
return TaskHelper.Done;
}

119
src/Squidex.Read/Schemas/WebhookInvoker.cs

@ -0,0 +1,119 @@
// ==========================================================================
// WebhookInvoker.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks;
using Squidex.Read.Schemas.Repositories;
namespace Squidex.Read.Schemas
{
public sealed class WebhookInvoker : IEventConsumer
{
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
private readonly ISchemaWebhookRepository webhookRepository;
private readonly ISemanticLog log;
private readonly JsonSerializer webhookSerializer;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^content-"; }
}
public WebhookInvoker(ISchemaWebhookRepository webhookRepository, JsonSerializer webhookSerializer, ISemanticLog log)
{
Guard.NotNull(webhookRepository, nameof(webhookRepository));
Guard.NotNull(webhookSerializer, nameof(webhookSerializer));
Guard.NotNull(log, nameof(log));
this.webhookRepository = webhookRepository;
this.webhookSerializer = webhookSerializer;
this.log = log;
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public async Task On(Envelope<IEvent> @event)
{
if (@event.Payload is ContentEvent contentEvent)
{
var hooks = await webhookRepository.QueryBySchemaAsync(contentEvent.SchemaId.Id);
if (hooks.Count > 0)
{
var payload = CreatePayload(@event);
foreach (var hook in hooks)
{
DispatchEventAsync(payload, hook).Forget();
}
}
}
}
private JObject CreatePayload(Envelope<IEvent> @event)
{
return new JObject(
new JProperty("type", @event.Payload.GetType().Name),
new JProperty("meta", JObject.FromObject(@event.Headers, webhookSerializer)),
new JProperty("data", JObject.FromObject(@event.Headers, webhookSerializer)));
}
private async Task DispatchEventAsync(JObject payload, ISchemaWebhookEntity webhook)
{
try
{
using (log.MeasureInformation(w => w
.WriteProperty("Action", "SendToHook")
.WriteProperty("Status", "Invoked")))
{
using (var client = new HttpClient())
{
client.Timeout = Timeout;
var message = new HttpRequestMessage(HttpMethod.Post, webhook.Url)
{
Content = new StringContent(payload.ToString(), Encoding.UTF8, "application/json")
};
message.Headers.TryAddWithoutValidation("X-SecurityToken", webhook.SecurityToken);
message.Headers.Add("User-Agent", "Squidex");
var response = await client.SendAsync(message);
response.EnsureSuccessStatusCode();
}
}
}
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("Action", "SendToHook")
.WriteProperty("Status", "Failed"));
}
}
}
}

4
src/Squidex/Config/Domain/ReadModule.cs

@ -73,6 +73,10 @@ namespace Squidex.Config.Domain
.As<IHistoryEventsCreator>()
.SingleInstance();
builder.RegisterType<WebhookInvoker>()
.AsSelf()
.SingleInstance();
builder.RegisterType<EdmModelBuilder>()
.AsSelf()
.SingleInstance();

16
src/Squidex/Config/Domain/StoreMongoDbModule.cs

@ -30,6 +30,7 @@ using Squidex.Read.MongoDb.History;
using Squidex.Read.MongoDb.Infrastructure;
using Squidex.Read.MongoDb.Schemas;
using Squidex.Read.MongoDb.Users;
using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Schemas.Services.Implementations;
using Squidex.Read.Users;
@ -160,6 +161,13 @@ namespace Squidex.Config.Domain
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoSchemaWebhookRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<ISchemaWebhookRepository>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.Register(c =>
new CompoundEventConsumer(
c.Resolve<MongoSchemaRepository>(),
@ -175,6 +183,14 @@ namespace Squidex.Config.Domain
.As<IEventConsumer>()
.AsSelf()
.SingleInstance();
builder.Register(c =>
new CompoundEventConsumer(
c.Resolve<WebhookInvoker>(),
c.Resolve<MongoSchemaWebhookRepository>()))
.As<IEventConsumer>()
.AsSelf()
.SingleInstance();
}
}
}

4
src/Squidex/Config/Domain/WriteModule.cs

@ -51,10 +51,6 @@ namespace Squidex.Config.Domain
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<ClientKeyGenerator>()
.AsSelf()
.SingleInstance();
builder.RegisterType<FieldRegistry>()
.AsSelf()
.SingleInstance();

28
tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs

@ -25,7 +25,7 @@ namespace Squidex.Infrastructure.CQRS.Events
[Fact]
public void Should_return_given_name()
{
var sut = new CompoundEventConsumer("consumer-name");
var sut = new CompoundEventConsumer("consumer-name", consumer1.Object);
Assert.Equal("consumer-name", sut.Name);
}
@ -33,25 +33,33 @@ namespace Squidex.Infrastructure.CQRS.Events
[Fact]
public void Should_return_first_inner_name()
{
const string name = "my-inner-consumer";
consumer1.Setup(x => x.Name).Returns(name);
consumer1.Setup(x => x.Name).Returns("my-inner-consumer");
var sut = new CompoundEventConsumer(consumer1.Object, consumer2.Object);
Assert.Equal(name, sut.Name);
Assert.Equal("my-inner-consumer", sut.Name);
}
[Fact]
public void Should_return_first_inner_filter()
public void Should_return_compound_filter()
{
const string filter = "my-inner-filter";
consumer1.Setup(x => x.EventsFilter).Returns("filter1");
consumer2.Setup(x => x.EventsFilter).Returns("filter2");
consumer1.Setup(x => x.EventsFilter).Returns(filter);
var sut = new CompoundEventConsumer("my", consumer1.Object, consumer2.Object);
var sut = new CompoundEventConsumer(consumer1.Object, consumer2.Object);
Assert.Equal("(filter1)|(filter2)", sut.EventsFilter);
}
[Fact]
public void Should_ignore_empty_filters()
{
consumer1.Setup(x => x.EventsFilter).Returns("filter1");
consumer2.Setup(x => x.EventsFilter).Returns("");
var sut = new CompoundEventConsumer("my", consumer1.Object, consumer2.Object);
Assert.Equal(filter, sut.EventsFilter);
Assert.Equal("(filter1)", sut.EventsFilter);
}
[Fact]

1
tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs

@ -8,7 +8,6 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;

Loading…
Cancel
Save