Browse Source

Usage trigger tests and improvements to persistence system.

pull/342/head
Sebastian Stehle 8 years ago
parent
commit
46f081f60f
  1. 5
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs
  2. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  3. 15
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs
  4. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs
  5. 18
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
  6. 23
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/UsageTriggerHandler.cs
  7. 2
      src/Squidex.Domain.Apps.Entities/Backup/BackupHandlerWithStore.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs
  9. 48
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs
  10. 120
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs
  11. 2
      src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
  12. 2
      src/Squidex.Infrastructure/Commands/LogSnapshotDomainObjectGrain.cs
  13. 43
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
  14. 20
      src/Squidex.Infrastructure/None.cs
  15. 2
      src/Squidex.Infrastructure/Orleans/GrainOfGuid{T}.cs
  16. 2
      src/Squidex.Infrastructure/Orleans/GrainOfString{T}.cs
  17. 2
      src/Squidex.Infrastructure/States/IPersistence.cs
  18. 11
      src/Squidex.Infrastructure/States/IStore.cs
  19. 7
      src/Squidex.Infrastructure/States/Persistence.cs
  20. 14
      src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs
  21. 15
      src/Squidex.Infrastructure/States/Store.cs
  22. 63
      src/Squidex.Infrastructure/States/StoreExtensions.cs
  23. 3
      src/Squidex/Config/Domain/RuleServices.cs
  24. 10
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  25. 41
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs
  26. 77
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs
  27. 54
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/UsageTriggerHandlerTests.cs
  28. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs
  29. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs
  30. 5
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByUserIndexGrainTests.cs
  31. 5
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesByAppIndexGrainTests.cs
  32. 5
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs
  33. 5
      tests/Squidex.Domain.Apps.Entities.Tests/Tags/TagGrainTests.cs
  34. 5
      tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs
  35. 2
      tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs
  36. 2
      tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectGrainTests.cs
  37. 6
      tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs
  38. 101
      tests/Squidex.Infrastructure.Tests/Orleans/GrainOfGuidTests.cs
  39. 101
      tests/Squidex.Infrastructure.Tests/Orleans/GrainOfStringTests.cs
  40. 45
      tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs
  41. 17
      tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs
  42. 2
      tools/Migrate_01/Rebuilder.cs

5
src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
@ -17,8 +16,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
Type TriggerType { get; } Type TriggerType { get; }
Task<bool> TriggersAsync(EnrichedEvent @event, RuleTrigger trigger); bool Trigger(EnrichedEvent @event, RuleTrigger trigger);
Task<bool> TriggersAsync(IEvent @event, RuleTrigger trigger); bool Trigger(IEvent @event, RuleTrigger trigger);
} }
} }

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return null; return null;
} }
if (!await triggerHandler.TriggersAsync(@event.Payload, rule.Trigger)) if (!triggerHandler.Trigger(@event.Payload, rule.Trigger))
{ {
return null; return null;
} }
@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var enrichedEvent = await eventEnricher.EnrichAsync(appEventEnvelope); var enrichedEvent = await eventEnricher.EnrichAsync(appEventEnvelope);
if (!await triggerHandler.TriggersAsync(enrichedEvent, rule.Trigger)) if (!triggerHandler.Trigger(enrichedEvent, rule.Trigger))
{ {
return null; return null;
} }

15
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
{ {
@ -24,21 +23,21 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get { return typeof(TTrigger); } get { return typeof(TTrigger); }
} }
Task<bool> IRuleTriggerHandler.TriggersAsync(EnrichedEvent @event, RuleTrigger trigger) bool IRuleTriggerHandler.Trigger(EnrichedEvent @event, RuleTrigger trigger)
{ {
return @event is TEnrichedEvent e ? TriggersAsync(e, (TTrigger)trigger) : TaskHelper.False; return @event is TEnrichedEvent e && Trigger(e, (TTrigger)trigger);
} }
Task<bool> IRuleTriggerHandler.TriggersAsync(IEvent @event, RuleTrigger trigger) bool IRuleTriggerHandler.Trigger(IEvent @event, RuleTrigger trigger)
{ {
return @event is TEvent e ? TriggersAsync(e, (TTrigger)trigger) : TaskHelper.False; return @event is TEvent e && Trigger(e, (TTrigger)trigger);
} }
protected abstract Task<bool> TriggersAsync(TEnrichedEvent @event, TTrigger trigger); protected abstract bool Trigger(TEnrichedEvent @event, TTrigger trigger);
protected virtual Task<bool> TriggersAsync(TEvent @event, TTrigger trigger) protected virtual bool Trigger(TEvent @event, TTrigger trigger)
{ {
return TaskHelper.True; return true;
} }
} }
} }

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs

@ -25,9 +25,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
this.scriptEngine = scriptEngine; this.scriptEngine = scriptEngine;
} }
protected override Task<bool> TriggersAsync(EnrichedAssetEvent @event, AssetChangedTriggerV2 trigger) protected override bool Trigger(EnrichedAssetEvent @event, AssetChangedTriggerV2 trigger)
{ {
return Task.FromResult(string.IsNullOrWhiteSpace(trigger.Condition) || scriptEngine.Evaluate("event", @event, trigger.Condition)); return string.IsNullOrWhiteSpace(trigger.Condition) || scriptEngine.Evaluate("event", @event, trigger.Condition);
} }
} }
} }

18
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs

@ -6,13 +6,11 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.HandleRules.Triggers namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{ {
@ -27,11 +25,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
this.scriptEngine = scriptEngine; this.scriptEngine = scriptEngine;
} }
protected override Task<bool> TriggersAsync(ContentEvent @event, ContentChangedTriggerV2 trigger) protected override bool Trigger(ContentEvent @event, ContentChangedTriggerV2 trigger)
{ {
if (trigger.HandleAll) if (trigger.HandleAll)
{ {
return TaskHelper.True; return true;
} }
if (trigger.Schemas != null) if (trigger.Schemas != null)
@ -40,19 +38,19 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{ {
if (MatchsSchema(schema, @event.SchemaId)) if (MatchsSchema(schema, @event.SchemaId))
{ {
return TaskHelper.True; return true;
} }
} }
} }
return TaskHelper.False; return false;
} }
protected override Task<bool> TriggersAsync(EnrichedContentEvent @event, ContentChangedTriggerV2 trigger) protected override bool Trigger(EnrichedContentEvent @event, ContentChangedTriggerV2 trigger)
{ {
if (trigger.HandleAll) if (trigger.HandleAll)
{ {
return TaskHelper.True; return true;
} }
if (trigger.Schemas != null) if (trigger.Schemas != null)
@ -61,12 +59,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{ {
if (MatchsSchema(schema, @event.SchemaId) && MatchsCondition(schema, @event)) if (MatchsSchema(schema, @event.SchemaId) && MatchsCondition(schema, @event))
{ {
return TaskHelper.True; return true;
} }
} }
} }
return TaskHelper.False; return false;
} }
private static bool MatchsSchema(ContentChangedTriggerSchemaV2 schema, NamedId<Guid> eventId) private static bool MatchsSchema(ContentChangedTriggerSchemaV2 schema, NamedId<Guid> eventId)

23
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/UsageTriggerHandler.cs

@ -0,0 +1,23 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{
public sealed class UsageTriggerHandler : RuleTriggerHandler<UsageTrigger, AppUsageExceeded, EnrichedUsageExceededEvent>
{
protected override bool Trigger(EnrichedUsageExceededEvent @event, UsageTrigger trigger)
{
return true;
}
}
}

2
src/Squidex.Domain.Apps.Entities/Backup/BackupHandlerWithStore.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Version = EtagVersion.Empty Version = EtagVersion.Empty
}; };
var persistence = store.WithSnapshotsAndEventSourcing<TState, Guid>(typeof(TGrain), key, s => state = s, e => var persistence = store.WithSnapshotsAndEventSourcing(typeof(TGrain), key, (TState s) => state = s, e =>
{ {
state = func(e, state); state = func(e, state);

2
src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
protected override Task ReadAsync(Type type, Guid id) protected override Task ReadAsync(Type type, Guid id)
{ {
persistence = store.WithEventSourcing<Guid>(GetType(), id, ApplyEvent); persistence = store.WithEventSourcing(GetType(), id, ApplyEvent);
return persistence.ReadAsync(); return persistence.ReadAsync();
} }

48
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs

@ -12,19 +12,15 @@ using Orleans;
using Orleans.Runtime; using Orleans.Runtime;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.UsageTracking; using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{ {
public sealed class UsageTrackerGrain : GrainOfString, IRemindable, IUsageTrackerGrain public sealed class UsageTrackerGrain : GrainOfString<UsageTrackerGrain.GrainState>, IRemindable, IUsageTrackerGrain
{ {
private readonly IStore<string> store;
private readonly IUsageTracker usageTracker; private readonly IUsageTracker usageTracker;
private IPersistence<State> persistence;
private State state;
public sealed class Target public sealed class Target
{ {
@ -36,46 +32,33 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
} }
[CollectionName("UsageTracker")] [CollectionName("UsageTracker")]
public sealed class State public sealed class GrainState
{ {
public Dictionary<NamedId<Guid>, Target> Targets { get; set; } = new Dictionary<NamedId<Guid>, Target>(); public Dictionary<NamedId<Guid>, Target> Targets { get; set; } = new Dictionary<NamedId<Guid>, Target>();
} }
public UsageTrackerGrain(IStore<string> store, IUsageTracker usageTracker) public UsageTrackerGrain(IStore<string> store, IUsageTracker usageTracker)
: base(store)
{ {
Guard.NotNull(store, nameof(store));
Guard.NotNull(usageTracker, nameof(usageTracker)); Guard.NotNull(usageTracker, nameof(usageTracker));
this.store = store;
this.usageTracker = usageTracker; this.usageTracker = usageTracker;
} }
public override Task OnActivateAsync(string key) protected override Task OnActivateAsync(string key)
{ {
DelayDeactivation(TimeSpan.FromDays(1)); DelayDeactivation(TimeSpan.FromDays(1));
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10)); RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
persistence = store.WithSnapshotsAndEventSourcing<State, string>(GetType(), key, ApplySnapshot, ApplyEvent); return Task.CompletedTask;
return persistence.ReadAsync();
}
private void ApplySnapshot(State s)
{
state = s;
}
private void ApplyEvent(Envelope<IEvent> @event)
{
} }
public async Task ReceiveReminder(string reminderName, TickStatus status) public async Task ReceiveReminder(string reminderName, TickStatus status)
{ {
var today = DateTime.Today; var today = DateTime.Today;
foreach (var kvp in state.Targets) foreach (var kvp in State.Targets)
{ {
var appId = kvp.Key; var appId = kvp.Key;
@ -91,15 +74,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
var @event = new AppUsageExceeded { AppId = appId, Current = usage, Limit = limit }; var @event = new AppUsageExceeded { AppId = appId, Current = usage, Limit = limit };
await persistence.WriteEventsAsync(new[] await Persistence.WriteEventAsync(@event);
{
Envelope.Create(@event)
});
} }
} }
} }
await persistence.WriteSnapshotAsync(state); await WriteStateAsync();
} }
private static bool IsSameMonth(DateTime lhs, DateTime rhs) private static bool IsSameMonth(DateTime lhs, DateTime rhs)
@ -111,33 +91,33 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{ {
UpdateTarget(appId, t => t.Enabled = true); UpdateTarget(appId, t => t.Enabled = true);
return persistence.WriteSnapshotAsync(state); return WriteStateAsync();
} }
public Task DeactivateTargetAsync(NamedId<Guid> appId) public Task DeactivateTargetAsync(NamedId<Guid> appId)
{ {
UpdateTarget(appId, t => t.Enabled = false); UpdateTarget(appId, t => t.Enabled = false);
return persistence.WriteSnapshotAsync(state); return WriteStateAsync();
} }
public Task AddTargetAsync(NamedId<Guid> appId, int limits) public Task AddTargetAsync(NamedId<Guid> appId, int limits)
{ {
UpdateTarget(appId, t => t.Limit = limits); UpdateTarget(appId, t => t.Limit = limits);
return persistence.WriteSnapshotAsync(state); return WriteStateAsync();
} }
public Task RemoveTargetAsync(NamedId<Guid> appId) public Task RemoveTargetAsync(NamedId<Guid> appId)
{ {
state.Targets.Remove(appId); State.Targets.Remove(appId);
return persistence.WriteSnapshotAsync(state); return WriteStateAsync();
} }
private void UpdateTarget(NamedId<Guid> appId, Action<Target> updater) private void UpdateTarget(NamedId<Guid> appId, Action<Target> updater)
{ {
updater(state.Targets.GetOrAddNew(appId));; updater(State.Targets.GetOrAddNew(appId));;
} }
} }
} }

120
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs

@ -1,69 +1,69 @@
// ========================================================================== //// ==========================================================================
// Squidex Headless CMS //// Squidex Headless CMS
// ========================================================================== //// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) //// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. //// All rights reserved. Licensed under the MIT license.
// ========================================================================== //// ==========================================================================
using System.Threading.Tasks; //using System.Threading.Tasks;
using Orleans; //using Orleans;
using Squidex.Domain.Apps.Core.HandleRules; //using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; //using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Triggers; //using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Events; //using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Rules; //using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure; //using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans; //using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Tasks; //using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking //namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{ //{
public sealed class UsageTriggerHandler : RuleTriggerHandler<UsageTrigger, AppEvent, EnrichedUsageExceededEvent> // public sealed class UsageTriggerHandler : RuleTriggerHandler<UsageTrigger, AppEvent, EnrichedUsageExceededEvent>
{ // {
private readonly IUsageTrackerGrain usageTrackerGrain; // private readonly IUsageTrackerGrain usageTrackerGrain;
public UsageTriggerHandler(IGrainFactory grainFactory) // public UsageTriggerHandler(IGrainFactory grainFactory)
{ // {
Guard.NotNull(grainFactory, nameof(grainFactory)); // Guard.NotNull(grainFactory, nameof(grainFactory));
usageTrackerGrain = grainFactory.GetGrain<IUsageTrackerGrain>(SingleGrain.Id); // usageTrackerGrain = grainFactory.GetGrain<IUsageTrackerGrain>(SingleGrain.Id);
} // }
protected override async Task<bool> TriggersAsync(AppEvent @event, UsageTrigger trigger) // protected override async Task<bool> Trigger(AppEvent @event, UsageTrigger trigger)
{ // {
switch (@event) // switch (@event)
{ // {
case RuleDeleted _: // case RuleDeleted _:
await usageTrackerGrain.RemoveTargetAsync(@event.AppId); // await usageTrackerGrain.RemoveTargetAsync(@event.AppId);
break; // break;
case RuleEnabled _: // case RuleEnabled _:
await usageTrackerGrain.ActivateTargetAsync(@event.AppId); // await usageTrackerGrain.ActivateTargetAsync(@event.AppId);
break; // break;
case RuleDisabled _: // case RuleDisabled _:
await usageTrackerGrain.DeactivateTargetAsync(@event.AppId); // await usageTrackerGrain.DeactivateTargetAsync(@event.AppId);
break; // break;
case RuleCreated ruleCreated: // case RuleCreated ruleCreated:
if (ruleCreated.Trigger is UsageTrigger createdTrigger) // if (ruleCreated.Trigger is UsageTrigger createdTrigger)
{ // {
await usageTrackerGrain.AddTargetAsync(ruleCreated.AppId, createdTrigger.Limit); // await usageTrackerGrain.AddTargetAsync(ruleCreated.AppId, createdTrigger.Limit);
} // }
break; // break;
case RuleUpdated ruleUpdated: // case RuleUpdated ruleUpdated:
if (ruleUpdated.Trigger is UsageTrigger updatedTrigger) // if (ruleUpdated.Trigger is UsageTrigger updatedTrigger)
{ // {
await usageTrackerGrain.AddTargetAsync(ruleUpdated.AppId, updatedTrigger.Limit); // await usageTrackerGrain.AddTargetAsync(ruleUpdated.AppId, updatedTrigger.Limit);
} // }
break; // break;
} // }
return @event is AppUsageExceeded; // return @event is AppUsageExceeded;
} // }
protected override Task<bool> TriggersAsync(EnrichedUsageExceededEvent @event, UsageTrigger trigger) // protected override Task<bool> Trigger(EnrichedUsageExceededEvent @event, UsageTrigger trigger)
{ // {
return TaskHelper.True; // return TaskHelper.True;
} // }
} // }
} //}

2
src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs

@ -47,7 +47,7 @@ namespace Squidex.Infrastructure.Commands
protected sealed override Task ReadAsync(Type type, Guid id) protected sealed override Task ReadAsync(Type type, Guid id)
{ {
persistence = store.WithSnapshotsAndEventSourcing<T, Guid>(GetType(), id, ApplySnapshot, ApplyEvent); persistence = store.WithSnapshotsAndEventSourcing(GetType(), id, new HandleSnapshot<T>(ApplySnapshot), ApplyEvent);
return persistence.ReadAsync(); return persistence.ReadAsync();
} }

2
src/Squidex.Infrastructure/Commands/LogSnapshotDomainObjectGrain.cs

@ -64,7 +64,7 @@ namespace Squidex.Infrastructure.Commands
protected sealed override Task ReadAsync(Type type, Guid id) protected sealed override Task ReadAsync(Type type, Guid id)
{ {
persistence = store.WithEventSourcing<Guid>(type, id, ApplyEvent); persistence = store.WithEventSourcing(type, id, ApplyEvent);
return persistence.ReadAsync(); return persistence.ReadAsync();
} }

43
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs

@ -17,18 +17,15 @@ using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing.Grains namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
public class EventConsumerGrain : GrainOfString, IEventConsumerGrain public class EventConsumerGrain : GrainOfString<EventConsumerState>, IEventConsumerGrain
{ {
private readonly EventConsumerFactory eventConsumerFactory; private readonly EventConsumerFactory eventConsumerFactory;
private readonly IStore<string> store;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly ISemanticLog log; private readonly ISemanticLog log;
private TaskScheduler scheduler; private TaskScheduler scheduler;
private IPersistence<EventConsumerState> persistence;
private IEventSubscription currentSubscription; private IEventSubscription currentSubscription;
private IEventConsumer eventConsumer; private IEventConsumer eventConsumer;
private EventConsumerState state = new EventConsumerState();
public EventConsumerGrain( public EventConsumerGrain(
EventConsumerFactory eventConsumerFactory, EventConsumerFactory eventConsumerFactory,
@ -36,18 +33,18 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
ISemanticLog log) ISemanticLog log)
: base(store)
{ {
Guard.NotNull(log, nameof(log));
Guard.NotNull(store, nameof(store));
Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(eventConsumerFactory, nameof(eventConsumerFactory)); Guard.NotNull(eventConsumerFactory, nameof(eventConsumerFactory));
Guard.NotNull(log, nameof(log));
this.log = log;
this.store = store;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventDataFormatter = eventDataFormatter;
this.eventConsumerFactory = eventConsumerFactory; this.eventConsumerFactory = eventConsumerFactory;
this.log = log;
} }
protected override Task OnActivateAsync(string key) protected override Task OnActivateAsync(string key)
@ -56,14 +53,12 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
eventConsumer = eventConsumerFactory(key); eventConsumer = eventConsumerFactory(key);
persistence = store.WithSnapshots<EventConsumerState, string>(GetType(), eventConsumer.Name, s => state = s); return Task.CompletedTask;
return persistence.ReadAsync();
} }
public Task<Immutable<EventConsumerInfo>> GetStateAsync() public Task<Immutable<EventConsumerInfo>> GetStateAsync()
{ {
return Task.FromResult(state.ToInfo(eventConsumer.Name).AsImmutable()); return Task.FromResult(State.ToInfo(eventConsumer.Name).AsImmutable());
} }
public Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent) public Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent)
@ -82,7 +77,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
await DispatchConsumerAsync(@event); await DispatchConsumerAsync(@event);
} }
state = state.Handled(storedEvent.Value.EventPosition); State = State.Handled(storedEvent.Value.EventPosition);
}); });
} }
@ -97,15 +92,15 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
Unsubscribe(); Unsubscribe();
state = state.Failed(exception.Value); State = State.Failed(exception.Value);
}); });
} }
public Task ActivateAsync() public Task ActivateAsync()
{ {
if (!state.IsStopped) if (!State.IsStopped)
{ {
Subscribe(state.Position); Subscribe(State.Position);
} }
return TaskHelper.Done; return TaskHelper.Done;
@ -113,22 +108,22 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
public Task StartAsync() public Task StartAsync()
{ {
if (!state.IsStopped) if (!State.IsStopped)
{ {
return TaskHelper.Done; return TaskHelper.Done;
} }
return DoAndUpdateStateAsync(() => return DoAndUpdateStateAsync(() =>
{ {
Subscribe(state.Position); Subscribe(State.Position);
state = state.Started(); State = State.Started();
}); });
} }
public Task StopAsync() public Task StopAsync()
{ {
if (state.IsStopped) if (State.IsStopped)
{ {
return TaskHelper.Done; return TaskHelper.Done;
} }
@ -137,7 +132,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
Unsubscribe(); Unsubscribe();
state = state.Stopped(); State = State.Stopped();
}); });
} }
@ -151,7 +146,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
Subscribe(null); Subscribe(null);
state = state.Reset(); State = State.Reset();
}); });
} }
@ -182,10 +177,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
.WriteProperty("status", "Failed") .WriteProperty("status", "Failed")
.WriteProperty("eventConsumer", eventConsumer.Name)); .WriteProperty("eventConsumer", eventConsumer.Name));
state = state.Failed(ex); State = State.Failed(ex);
} }
await persistence.WriteSnapshotAsync(state); await WriteStateAsync();
} }
private async Task ClearAsync() private async Task ClearAsync()

20
src/Squidex.Infrastructure/None.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Infrastructure
{
public sealed class None
{
public static readonly Type Type = typeof(None);
private None()
{
}
}
}

2
src/Squidex.Infrastructure/Orleans/GrainOfGuid{T}.cs

@ -43,7 +43,7 @@ namespace Squidex.Infrastructure.Orleans
{ {
Key = key; Key = key;
persistence = store.WithSnapshots<T, Guid>(GetType(), key, ApplyState); persistence = store.WithSnapshots(GetType(), key, new HandleSnapshot<T>(ApplyState));
await persistence.ReadAsync(); await persistence.ReadAsync();

2
src/Squidex.Infrastructure/Orleans/GrainOfString{T}.cs

@ -42,7 +42,7 @@ namespace Squidex.Infrastructure.Orleans
{ {
Key = key; Key = key;
persistence = store.WithSnapshots<T, string>(GetType(), key, ApplyState); persistence = store.WithSnapshots(GetType(), key, new HandleSnapshot<T>(ApplyState));
await persistence.ReadAsync(); await persistence.ReadAsync();

2
src/Squidex.Infrastructure/States/IPersistence.cs

@ -7,7 +7,7 @@
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public interface IPersistence : IPersistence<object> public interface IPersistence : IPersistence<None>
{ {
} }
} }

11
src/Squidex.Infrastructure/States/IStore.cs

@ -6,18 +6,21 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public delegate void HandleEvent(Envelope<IEvent> @event);
public delegate void HandleSnapshot<T>(T state);
public interface IStore<in TKey> public interface IStore<in TKey>
{ {
IPersistence WithEventSourcing(Type owner, TKey key, Func<Envelope<IEvent>, Task> applyEvent); IPersistence WithEventSourcing(Type owner, TKey key, HandleEvent applyEvent);
IPersistence<TState> WithSnapshots<TState>(Type owner, TKey key, Func<TState, Task> applySnapshot); IPersistence<TState> WithSnapshots<TState>(Type owner, TKey key, HandleSnapshot<TState> applySnapshot);
IPersistence<TState> WithSnapshotsAndEventSourcing<TState>(Type owner, TKey key, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent); IPersistence<TState> WithSnapshotsAndEventSourcing<TState>(Type owner, TKey key, HandleSnapshot<TState> applySnapshot, HandleEvent applyEvent);
ISnapshotStore<TState, TKey> GetSnapshotStore<TState>(); ISnapshotStore<TState, TKey> GetSnapshotStore<TState>();
} }

7
src/Squidex.Infrastructure/States/Persistence.cs

@ -6,19 +6,18 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
internal sealed class Persistence<TKey> : Persistence<object, TKey>, IPersistence internal sealed class Persistence<TKey> : Persistence<None, TKey>, IPersistence
{ {
public Persistence(TKey ownerKey, Type ownerType, public Persistence(TKey ownerKey, Type ownerType,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
ISnapshotStore<object, TKey> snapshotStore, ISnapshotStore<None, TKey> snapshotStore,
IStreamNameResolver streamNameResolver, IStreamNameResolver streamNameResolver,
Func<Envelope<IEvent>, Task> applyEvent) HandleEvent applyEvent)
: base(ownerKey, ownerType, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, PersistenceMode.EventSourcing, null, applyEvent) : base(ownerKey, ownerType, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, PersistenceMode.EventSourcing, null, applyEvent)
{ {
} }

14
src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs

@ -24,8 +24,8 @@ namespace Squidex.Infrastructure.States
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
private readonly PersistenceMode persistenceMode; private readonly PersistenceMode persistenceMode;
private readonly Func<TSnapshot, Task> applyState; private readonly HandleSnapshot<TSnapshot> applyState;
private readonly Func<Envelope<IEvent>, Task> applyEvent; private readonly HandleEvent applyEvent;
private long versionSnapshot = EtagVersion.Empty; private long versionSnapshot = EtagVersion.Empty;
private long versionEvents = EtagVersion.Empty; private long versionEvents = EtagVersion.Empty;
private long version; private long version;
@ -41,8 +41,8 @@ namespace Squidex.Infrastructure.States
ISnapshotStore<TSnapshot, TKey> snapshotStore, ISnapshotStore<TSnapshot, TKey> snapshotStore,
IStreamNameResolver streamNameResolver, IStreamNameResolver streamNameResolver,
PersistenceMode persistenceMode, PersistenceMode persistenceMode,
Func<TSnapshot, Task> applyState, HandleSnapshot<TSnapshot> applyState,
Func<Envelope<IEvent>, Task> applyEvent) HandleEvent applyEvent)
{ {
this.ownerKey = ownerKey; this.ownerKey = ownerKey;
this.ownerType = ownerType; this.ownerType = ownerType;
@ -94,7 +94,7 @@ namespace Squidex.Infrastructure.States
if (applyState != null && position >= 0) if (applyState != null && position >= 0)
{ {
await applyState(state); applyState(state);
} }
} }
} }
@ -116,9 +116,9 @@ namespace Squidex.Infrastructure.States
var parsedEvent = ParseKnownEvent(@event); var parsedEvent = ParseKnownEvent(@event);
if (parsedEvent != null && applyEvent != null) if (applyEvent != null && parsedEvent != null)
{ {
await applyEvent(parsedEvent); applyEvent(parsedEvent);
} }
} }
} }

15
src/Squidex.Infrastructure/States/Store.cs

@ -30,26 +30,31 @@ namespace Squidex.Infrastructure.States
this.streamNameResolver = streamNameResolver; this.streamNameResolver = streamNameResolver;
} }
public IPersistence<TState> WithSnapshots<TState>(Type owner, TKey key, Func<TState, Task> applySnapshot) public IPersistence WithEventSourcing(Type owner, TKey key, HandleEvent applyEvent)
{
return CreatePersistence(owner, key, applyEvent);
}
public IPersistence<TState> WithSnapshots<TState>(Type owner, TKey key, HandleSnapshot<TState> applySnapshot)
{ {
return CreatePersistence(owner, key, PersistenceMode.Snapshots, applySnapshot, null); return CreatePersistence(owner, key, PersistenceMode.Snapshots, applySnapshot, null);
} }
public IPersistence<TState> WithSnapshotsAndEventSourcing<TState>(Type owner, TKey key, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent) public IPersistence<TState> WithSnapshotsAndEventSourcing<TState>(Type owner, TKey key, HandleSnapshot<TState> applySnapshot, HandleEvent applyEvent)
{ {
return CreatePersistence(owner, key, PersistenceMode.SnapshotsAndEventSourcing, applySnapshot, applyEvent); return CreatePersistence(owner, key, PersistenceMode.SnapshotsAndEventSourcing, applySnapshot, applyEvent);
} }
public IPersistence WithEventSourcing(Type owner, TKey key, Func<Envelope<IEvent>, Task> applyEvent) private IPersistence CreatePersistence(Type owner, TKey key, HandleEvent applyEvent)
{ {
Guard.NotNull(key, nameof(key)); Guard.NotNull(key, nameof(key));
var snapshotStore = GetSnapshotStore<object>(); var snapshotStore = GetSnapshotStore<None>();
return new Persistence<TKey>(key, owner, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, applyEvent); return new Persistence<TKey>(key, owner, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, applyEvent);
} }
private IPersistence<TState> CreatePersistence<TState>(Type owner, TKey key, PersistenceMode mode, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent) private IPersistence<TState> CreatePersistence<TState>(Type owner, TKey key, PersistenceMode mode, HandleSnapshot<TState> applySnapshot, HandleEvent applyEvent)
{ {
Guard.NotNull(key, nameof(key)); Guard.NotNull(key, nameof(key));

63
src/Squidex.Infrastructure/States/StoreExtensions.cs

@ -5,83 +5,36 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public static class StoreExtensions public static class StoreExtensions
{ {
public static IPersistence WithEventSourcing<TOwner, TKey>(this IStore<TKey> store, TKey key, Func<Envelope<IEvent>, Task> applyEvent) public static Task WriteEventAsync<T>(this IPersistence<T> persistence, Envelope<IEvent> @event)
{
return store.WithEventSourcing(typeof(TOwner), key, applyEvent);
}
public static IPersistence<TState> WithSnapshots<TOwner, TState, TKey>(this IStore<TKey> store, TKey key, Func<TState, Task> applySnapshot)
{
return store.WithSnapshots(typeof(TOwner), key, applySnapshot);
}
public static IPersistence<TState> WithSnapshotsAndEventSourcing<TOwner, TState, TKey>(this IStore<TKey> store, TKey key, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent)
{
return store.WithSnapshotsAndEventSourcing(typeof(TOwner), key, applySnapshot, applyEvent);
}
public static IPersistence WithEventSourcing<TKey>(this IStore<TKey> store, Type owner, TKey key, Action<Envelope<IEvent>> applyEvent)
{
return store.WithEventSourcing(owner, key, applyEvent.ToAsync());
}
public static IPersistence<TState> WithSnapshots<TState, TKey>(this IStore<TKey> store, Type owner, TKey key, Action<TState> applySnapshot)
{
return store.WithSnapshots(owner, key, applySnapshot.ToAsync());
}
public static IPersistence<TState> WithSnapshotsAndEventSourcing<TState, TKey>(this IStore<TKey> store, Type owner, TKey key, Action<TState> applySnapshot, Action<Envelope<IEvent>> applyEvent)
{
return store.WithSnapshotsAndEventSourcing(owner, key, applySnapshot.ToAsync(), applyEvent.ToAsync());
}
public static IPersistence WithEventSourcing<TOwner, TKey>(this IStore<TKey> store, TKey key, Action<Envelope<IEvent>> applyEvent)
{
return store.WithEventSourcing(typeof(TOwner), key, applyEvent.ToAsync());
}
public static IPersistence<TState> WithSnapshots<TOwner, TState, TKey>(this IStore<TKey> store, TKey key, Action<TState> applySnapshot)
{
return store.WithSnapshots(typeof(TOwner), key, applySnapshot.ToAsync());
}
public static IPersistence<TState> WithSnapshotsAndEventSourcing<TOwner, TState, TKey>(this IStore<TKey> store, TKey key, Action<TState> applySnapshot, Action<Envelope<IEvent>> applyEvent)
{
return store.WithSnapshotsAndEventSourcing(typeof(TOwner), key, applySnapshot.ToAsync(), applyEvent.ToAsync());
}
public static Task WriteEventAsync<T>(IPersistence<T> persistence, Envelope<IEvent> @event)
{ {
return persistence.WriteEventsAsync(new[] { @event }); return persistence.WriteEventsAsync(new[] { @event });
} }
public static Task WriteEventAsync<T>(IPersistence<T> persistence, IEvent @event) public static Task WriteEventAsync<T>(this IPersistence<T> persistence, IEvent @event)
{ {
return persistence.WriteEventsAsync(new[] { Envelope.Create(@event) }); return persistence.WriteEventsAsync(new[] { Envelope.Create(@event) });
} }
public static Task ClearSnapshotsAsync<TKey, TState>(this IStore<TKey> store) public static Task ClearSnapshotsAsync<TKey, TSnapshot>(this IStore<TKey> store)
{ {
return store.GetSnapshotStore<TState>().ClearAsync(); return store.GetSnapshotStore<TSnapshot>().ClearAsync();
} }
public static Task RemoveSnapshotAsync<TKey, TState>(this IStore<TKey> store, TKey key) public static Task RemoveSnapshotAsync<TKey, TSnapshot>(this IStore<TKey> store, TKey key)
{ {
return store.GetSnapshotStore<TState>().RemoveAsync(key); return store.GetSnapshotStore<TSnapshot>().RemoveAsync(key);
} }
public static async Task<TState> GetSnapshotAsync<TKey, TState>(this IStore<TKey> store, TKey key) public static async Task<TSnapshot> GetSnapshotAsync<TKey, TSnapshot>(this IStore<TKey> store, TKey key)
{ {
var result = await store.GetSnapshotStore<TState>().ReadAsync(key); var result = await store.GetSnapshotStore<TSnapshot>().ReadAsync(key);
return result.Value; return result.Value;
} }

3
src/Squidex/Config/Domain/RuleServices.cs

@ -27,6 +27,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ContentChangedTriggerHandler>() services.AddSingletonAs<ContentChangedTriggerHandler>()
.As<IRuleTriggerHandler>(); .As<IRuleTriggerHandler>();
services.AddSingletonAs<UsageTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<RuleEnqueuer>() services.AddSingletonAs<RuleEnqueuer>()
.As<IEventConsumer>(); .As<IEventConsumer>();

10
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs

@ -146,7 +146,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var ruleConfig = ValidRule(); var ruleConfig = ValidRule();
var ruleEnvelope = Envelope.Create(new ContentCreated()); var ruleEnvelope = Envelope.Create(new ContentCreated());
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<IEvent>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Trigger(A<IEvent>.Ignored, ruleConfig.Trigger))
.Returns(false); .Returns(false);
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
@ -163,10 +163,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var ruleConfig = ValidRule(); var ruleConfig = ValidRule();
var ruleEnvelope = Envelope.Create(new ContentCreated()); var ruleEnvelope = Envelope.Create(new ContentCreated());
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<IEvent>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Trigger(A<IEvent>.Ignored, ruleConfig.Trigger))
.Returns(true); .Returns(true);
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<EnrichedEvent>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Trigger(A<EnrichedEvent>.Ignored, ruleConfig.Trigger))
.Returns(false); .Returns(false);
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
@ -215,10 +215,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.Returns(now); .Returns(now);
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<EnrichedEvent>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Trigger(A<EnrichedEvent>.Ignored, ruleConfig.Trigger))
.Returns(true); .Returns(true);
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<IEvent>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Trigger(A<IEvent>.Ignored, ruleConfig.Trigger))
.Returns(true); .Returns(true);
A.CallTo(() => ruleActionHandler.CreateJobAsync(A<EnrichedEvent>.Ignored, ruleConfig.Action)) A.CallTo(() => ruleActionHandler.CreateJobAsync(A<EnrichedEvent>.Ignored, ruleConfig.Action))

41
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -36,76 +35,76 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
} }
[Fact] [Fact]
public Task Should_not_trigger_precheck_when_event_type_not_correct() public void Should_not_trigger_precheck_when_event_type_not_correct()
{ {
return TestForConditionAsync(string.Empty, async trigger => TestForCondition(string.Empty, trigger =>
{ {
var result = await sut.TriggersAsync(new ContentCreated(), trigger); var result = sut.Trigger(new ContentCreated(), trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_precheck_when_event_type_correct() public void Should_trigger_precheck_when_event_type_correct()
{ {
return TestForConditionAsync(string.Empty, async trigger => TestForCondition(string.Empty, trigger =>
{ {
var result = await sut.TriggersAsync(new AssetCreated(), trigger); var result = sut.Trigger(new AssetCreated(), trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_check_when_event_type_not_correct() public void Should_not_trigger_check_when_event_type_not_correct()
{ {
return TestForConditionAsync(string.Empty, async trigger => TestForCondition(string.Empty, trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedContentEvent(), trigger); var result = sut.Trigger(new EnrichedContentEvent(), trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_check_when_condition_is_empty() public void Should_trigger_check_when_condition_is_empty()
{ {
return TestForConditionAsync(string.Empty, async trigger => TestForCondition(string.Empty, trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger); var result = sut.Trigger(new EnrichedAssetEvent(), trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_check_when_condition_matchs() public void Should_trigger_check_when_condition_matchs()
{ {
return TestForConditionAsync("true", async trigger => TestForCondition("true", trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger); var result = sut.Trigger(new EnrichedAssetEvent(), trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_check_when_condition_does_not_matchs() public void Should_not_trigger_check_when_condition_does_not_matchs()
{ {
return TestForConditionAsync("false", async trigger => TestForCondition("false", trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger); var result = sut.Trigger(new EnrichedAssetEvent(), trigger);
Assert.False(result); Assert.False(result);
}); });
} }
private async Task TestForConditionAsync(string condition, Func<AssetChangedTriggerV2, Task> action) private void TestForCondition(string condition, Action<AssetChangedTriggerV2> action)
{ {
var trigger = new AssetChangedTriggerV2 { Condition = condition }; var trigger = new AssetChangedTriggerV2 { Condition = condition };
await action(trigger); action(trigger);
if (string.IsNullOrWhiteSpace(condition)) if (string.IsNullOrWhiteSpace(condition))
{ {

77
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -44,138 +43,138 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
} }
[Fact] [Fact]
public Task Should_not_trigger_precheck_when_event_type_not_correct() public void Should_not_trigger_precheck_when_event_type_not_correct()
{ {
return TestForTriggerAsync(handleAll: true, schemaId: null, condition: null, action: async trigger => TestForTrigger(handleAll: true, schemaId: null, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new AssetCreated(), trigger); var result = sut.Trigger(new AssetCreated(), trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_precheck_when_trigger_contains_no_schemas() public void Should_not_trigger_precheck_when_trigger_contains_no_schemas()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: null, condition: null, action: async trigger => TestForTrigger(handleAll: false, schemaId: null, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_precheck_when_handling_all_events() public void Should_trigger_precheck_when_handling_all_events()
{ {
return TestForTriggerAsync(handleAll: true, schemaId: SchemaMatch, condition: null, action: async trigger => TestForTrigger(handleAll: true, schemaId: SchemaMatch, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_precheck_when_condition_is_empty() public void Should_trigger_precheck_when_condition_is_empty()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: async trigger => TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: trigger =>
{ {
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_precheck_when_schema_id_does_not_match() public void Should_not_trigger_precheck_when_schema_id_does_not_match()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: async trigger => TestForTrigger(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_check_when_event_type_not_correct() public void Should_not_trigger_check_when_event_type_not_correct()
{ {
return TestForTriggerAsync(handleAll: true, schemaId: null, condition: null, action: async trigger => TestForTrigger(handleAll: true, schemaId: null, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger); var result = sut.Trigger(new EnrichedAssetEvent(), trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_check_when_trigger_contains_no_schemas() public void Should_not_trigger_check_when_trigger_contains_no_schemas()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: null, condition: null, action: async trigger => TestForTrigger(handleAll: false, schemaId: null, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_check_when_handling_all_events() public void Should_trigger_check_when_handling_all_events()
{ {
return TestForTriggerAsync(handleAll: true, schemaId: SchemaMatch, condition: null, action: async trigger => TestForTrigger(handleAll: true, schemaId: SchemaMatch, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_check_when_condition_is_empty() public void Should_trigger_check_when_condition_is_empty()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: async trigger => TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_trigger_check_when_condition_matchs() public void Should_trigger_check_when_condition_matchs()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: "true", action: async trigger => TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: "true", action: trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.True(result); Assert.True(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_check_when_schema_id_does_not_match() public void Should_not_trigger_check_when_schema_id_does_not_match()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: async trigger => TestForTrigger(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.False(result); Assert.False(result);
}); });
} }
[Fact] [Fact]
public Task Should_not_trigger_check_when_condition_does_not_matchs() public void Should_not_trigger_check_when_condition_does_not_matchs()
{ {
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: "false", action: async trigger => TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: "false", action: trigger =>
{ {
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger); var result = sut.Trigger(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.False(result); Assert.False(result);
}); });
} }
private async Task TestForTriggerAsync(bool handleAll, NamedId<Guid> schemaId, string condition, Func<ContentChangedTriggerV2, Task> action) private void TestForTrigger(bool handleAll, NamedId<Guid> schemaId, string condition, Action<ContentChangedTriggerV2> action)
{ {
var trigger = new ContentChangedTriggerV2 { HandleAll = handleAll }; var trigger = new ContentChangedTriggerV2 { HandleAll = handleAll };
@ -190,7 +189,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
}); });
} }
await action(trigger); action(trigger);
if (string.IsNullOrWhiteSpace(condition)) if (string.IsNullOrWhiteSpace(condition))
{ {

54
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/UsageTriggerHandlerTests.cs

@ -0,0 +1,54 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.HandleRules.Triggers;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
{
public class UsageTriggerHandlerTests
{
private readonly IRuleTriggerHandler sut = new UsageTriggerHandler();
[Fact]
public void Should_not_trigger_precheck_when_event_type_not_correct()
{
var result = sut.Trigger(new ContentCreated(), new UsageTrigger());
Assert.False(result);
}
[Fact]
public void Should_trigger_precheck_when_event_type_correct()
{
var result = sut.Trigger(new AppUsageExceeded(), new UsageTrigger());
Assert.True(result);
}
[Fact]
public void Should_not_trigger_check_when_event_type_not_correct()
{
var result = sut.Trigger(new EnrichedContentEvent(), new UsageTrigger());
Assert.False(result);
}
[Fact]
public void Should_trigger_check_when_type_correct()
{
var result = sut.Trigger(new AppUsageExceeded(), new UsageTrigger());
Assert.True(result);
}
}
}

2
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs

@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
public AppUISettingsGrainTests() public AppUISettingsGrainTests()
{ {
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<Guid>.Ignored, A<Func<AppUISettingsGrain.GrainState, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots(typeof(AppUISettingsGrain), Guid.Empty, A<HandleSnapshot<AppUISettingsGrain.GrainState>>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new AppUISettingsGrain(store); sut = new AppUISettingsGrain(store);

2
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs

@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
public AppsByNameIndexGrainTests() public AppsByNameIndexGrainTests()
{ {
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<string>.Ignored, A<Func<AppsByNameIndexGrain.GrainState, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots(typeof(AppsByNameIndexGrain), SingleGrain.Id, A<HandleSnapshot<AppsByNameIndexGrain.GrainState>>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new AppsByNameIndexGrain(store); sut = new AppsByNameIndexGrain(store);

5
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByUserIndexGrainTests.cs

@ -21,15 +21,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
private readonly IPersistence<AppsByUserIndexGrain.GrainState> persistence = A.Fake<IPersistence<AppsByUserIndexGrain.GrainState>>(); private readonly IPersistence<AppsByUserIndexGrain.GrainState> persistence = A.Fake<IPersistence<AppsByUserIndexGrain.GrainState>>();
private readonly Guid appId1 = Guid.NewGuid(); private readonly Guid appId1 = Guid.NewGuid();
private readonly Guid appId2 = Guid.NewGuid(); private readonly Guid appId2 = Guid.NewGuid();
private readonly string userId = "user";
private readonly AppsByUserIndexGrain sut; private readonly AppsByUserIndexGrain sut;
public AppsByUserIndexGrainTests() public AppsByUserIndexGrainTests()
{ {
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<string>.Ignored, A<Func<AppsByUserIndexGrain.GrainState, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots(typeof(AppsByUserIndexGrain), userId, A<HandleSnapshot<AppsByUserIndexGrain.GrainState>>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new AppsByUserIndexGrain(store); sut = new AppsByUserIndexGrain(store);
sut.ActivateAsync("user").Wait(); sut.ActivateAsync(userId).Wait();
} }
[Fact] [Fact]

5
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesByAppIndexGrainTests.cs

@ -18,17 +18,18 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes
{ {
private readonly IStore<Guid> store = A.Fake<IStore<Guid>>(); private readonly IStore<Guid> store = A.Fake<IStore<Guid>>();
private readonly IPersistence<RulesByAppIndexGrain.GrainState> persistence = A.Fake<IPersistence<RulesByAppIndexGrain.GrainState>>(); private readonly IPersistence<RulesByAppIndexGrain.GrainState> persistence = A.Fake<IPersistence<RulesByAppIndexGrain.GrainState>>();
private readonly Guid appId = Guid.NewGuid();
private readonly Guid ruleId1 = Guid.NewGuid(); private readonly Guid ruleId1 = Guid.NewGuid();
private readonly Guid ruleId2 = Guid.NewGuid(); private readonly Guid ruleId2 = Guid.NewGuid();
private readonly RulesByAppIndexGrain sut; private readonly RulesByAppIndexGrain sut;
public RulesByAppIndexGrainTests() public RulesByAppIndexGrainTests()
{ {
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<Guid>.Ignored, A<Func<RulesByAppIndexGrain.GrainState, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots(typeof(RulesByAppIndexGrain), appId, A<HandleSnapshot<RulesByAppIndexGrain.GrainState>>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new RulesByAppIndexGrain(store); sut = new RulesByAppIndexGrain(store);
sut.ActivateAsync(Guid.NewGuid()).Wait(); sut.ActivateAsync(appId).Wait();
} }
[Fact] [Fact]

5
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs

@ -18,6 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
{ {
private readonly IStore<Guid> store = A.Fake<IStore<Guid>>(); private readonly IStore<Guid> store = A.Fake<IStore<Guid>>();
private readonly IPersistence<SchemasByAppIndexGrain.GrainState> persistence = A.Fake<IPersistence<SchemasByAppIndexGrain.GrainState>>(); private readonly IPersistence<SchemasByAppIndexGrain.GrainState> persistence = A.Fake<IPersistence<SchemasByAppIndexGrain.GrainState>>();
private readonly Guid appId = Guid.NewGuid();
private readonly Guid schemaId1 = Guid.NewGuid(); private readonly Guid schemaId1 = Guid.NewGuid();
private readonly Guid schemaId2 = Guid.NewGuid(); private readonly Guid schemaId2 = Guid.NewGuid();
private readonly string schemaName1 = "my-schema1"; private readonly string schemaName1 = "my-schema1";
@ -26,11 +27,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
public SchemasByAppIndexGrainTests() public SchemasByAppIndexGrainTests()
{ {
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<Guid>.Ignored, A<Func<SchemasByAppIndexGrain.GrainState, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots(typeof(SchemasByAppIndexGrain), appId, A<HandleSnapshot<SchemasByAppIndexGrain.GrainState>>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new SchemasByAppIndexGrain(store); sut = new SchemasByAppIndexGrain(store);
sut.ActivateAsync(Guid.NewGuid()).Wait(); sut.ActivateAsync(appId).Wait();
} }
[Fact] [Fact]

5
tests/Squidex.Domain.Apps.Entities.Tests/Tags/TagGrainTests.cs

@ -20,15 +20,16 @@ namespace Squidex.Domain.Apps.Entities.Tags
{ {
private readonly IStore<string> store = A.Fake<IStore<string>>(); private readonly IStore<string> store = A.Fake<IStore<string>>();
private readonly IPersistence<TagGrain.GrainState> persistence = A.Fake<IPersistence<TagGrain.GrainState>>(); private readonly IPersistence<TagGrain.GrainState> persistence = A.Fake<IPersistence<TagGrain.GrainState>>();
private readonly string id = Guid.NewGuid().ToString();
private readonly TagGrain sut; private readonly TagGrain sut;
public TagGrainTests() public TagGrainTests()
{ {
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<string>.Ignored, A<Func<TagGrain.GrainState, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots(typeof(TagGrain), id, A<HandleSnapshot<TagGrain.GrainState>>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new TagGrain(store); sut = new TagGrain(store);
sut.ActivateAsync(string.Empty).Wait(); sut.ActivateAsync(id).Wait();
} }
[Fact] [Fact]

5
tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -58,10 +57,10 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers
protected HandlerTestBase() protected HandlerTestBase()
{ {
A.CallTo(() => store.WithSnapshotsAndEventSourcing(A<Type>.Ignored, Id, A<Func<TState, Task>>.Ignored, A<Func<Envelope<IEvent>, Task>>.Ignored)) A.CallTo(() => store.WithSnapshotsAndEventSourcing(A<Type>.Ignored, Id, A<HandleSnapshot<TState>>.Ignored, A<HandleEvent>.Ignored))
.Returns(persistence1); .Returns(persistence1);
A.CallTo(() => store.WithEventSourcing(A<Type>.Ignored, Id, A<Func<Envelope<IEvent>, Task>>.Ignored)) A.CallTo(() => store.WithEventSourcing(A<Type>.Ignored, Id, A<HandleEvent>.Ignored))
.Returns(persistence2); .Returns(persistence2);
A.CallTo(() => persistence1.WriteEventsAsync(A<IEnumerable<Envelope<IEvent>>>.Ignored)) A.CallTo(() => persistence1.WriteEventsAsync(A<IEnumerable<Envelope<IEvent>>>.Ignored))

2
tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs

@ -109,7 +109,7 @@ namespace Squidex.Infrastructure.Commands
public DomainObjectGrainTests() public DomainObjectGrainTests()
{ {
A.CallTo(() => store.WithSnapshotsAndEventSourcing(typeof(MyDomainObject), id, A<Func<MyDomainState, Task>>.Ignored, A<Func<Envelope<IEvent>, Task>>.Ignored)) A.CallTo(() => store.WithSnapshotsAndEventSourcing(typeof(MyDomainObject), id, A<HandleSnapshot<MyDomainState>>.Ignored, A<HandleEvent>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new MyDomainObject(store); sut = new MyDomainObject(store);

2
tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectGrainTests.cs

@ -104,7 +104,7 @@ namespace Squidex.Infrastructure.Commands
public LogSnapshotDomainObjectGrainTests() public LogSnapshotDomainObjectGrainTests()
{ {
A.CallTo(() => store.WithEventSourcing(typeof(MyDomainObject), id, A<Func<Envelope<IEvent>, Task>>.Ignored)) A.CallTo(() => store.WithEventSourcing(typeof(MyDomainObject), id, A<HandleEvent>.Ignored))
.Returns(persistence); .Returns(persistence);
A.CallTo(() => store.GetSnapshotStore<MyDomainState>()) A.CallTo(() => store.GetSnapshotStore<MyDomainState>())

6
tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs

@ -54,7 +54,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
private readonly EventConsumerGrain sut; private readonly EventConsumerGrain sut;
private readonly string consumerName; private readonly string consumerName;
private readonly string initialPosition = Guid.NewGuid().ToString(); private readonly string initialPosition = Guid.NewGuid().ToString();
private Func<EventConsumerState, Task> apply; private HandleSnapshot<EventConsumerState> apply;
private EventConsumerState state = new EventConsumerState(); private EventConsumerState state = new EventConsumerState();
public EventConsumerGrainTests() public EventConsumerGrainTests()
@ -63,8 +63,8 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
consumerName = eventConsumer.GetType().Name; consumerName = eventConsumer.GetType().Name;
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, consumerName, A<Func<EventConsumerState, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, consumerName, A<HandleSnapshot<EventConsumerState>>.Ignored))
.Invokes(new Action<Type, string, Func<EventConsumerState, Task>>((t, key, a) => .Invokes(new Action<Type, string, HandleSnapshot<EventConsumerState>>((t, key, a) =>
{ {
apply = a; apply = a;
})) }))

101
tests/Squidex.Infrastructure.Tests/Orleans/GrainOfGuidTests.cs

@ -0,0 +1,101 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Infrastructure.States;
using Xunit;
namespace Squidex.Infrastructure.Orleans
{
public class GrainOfGuidTests
{
private readonly IPersistence<MyGrain.GrainState> persistence = A.Fake<IPersistence<MyGrain.GrainState>>();
private readonly IStore<Guid> store = A.Fake<IStore<Guid>>();
private readonly Guid id = Guid.NewGuid();
private readonly MyGrain sut;
private HandleSnapshot<MyGrain.GrainState> read;
public sealed class MyGrain : GrainOfGuid<MyGrain.GrainState>
{
public sealed class GrainState
{
public Guid Id { get; set; }
}
public GrainState PublicState
{
get { return State; }
}
public MyGrain(IStore<Guid> store)
: base(store)
{
}
public Task PublicWriteAsync()
{
return WriteStateAsync();
}
public Task PublicClearAsync()
{
return ClearStateAsync();
}
}
public GrainOfGuidTests()
{
A.CallTo(() => persistence.ReadAsync(EtagVersion.Any))
.Invokes(_ =>
{
read(new MyGrain.GrainState { Id = id });
});
A.CallTo(() => store.WithSnapshots(typeof(MyGrain), id, A<HandleSnapshot<MyGrain.GrainState>>.Ignored))
.Invokes(new Action<Type, Guid, HandleSnapshot<MyGrain.GrainState>>((type, id, callback) =>
{
read = callback;
}))
.Returns(persistence);
sut = new MyGrain(store);
}
[Fact]
public async Task Should_read_on_activate()
{
await sut.ActivateAsync(id);
Assert.Equal(id, sut.PublicState.Id);
A.CallTo(() => persistence.ReadAsync(EtagVersion.Any))
.MustHaveHappened();
}
[Fact]
public async Task Should_invoke_persistence_on_write()
{
await sut.ActivateAsync(id);
await sut.PublicWriteAsync();
A.CallTo(() => persistence.WriteSnapshotAsync(sut.PublicState))
.MustHaveHappened();
}
[Fact]
public async Task Should_invoke_persistence_on_clear()
{
await sut.ActivateAsync(id);
await sut.PublicClearAsync();
A.CallTo(() => persistence.DeleteAsync())
.MustHaveHappened();
}
}
}

101
tests/Squidex.Infrastructure.Tests/Orleans/GrainOfStringTests.cs

@ -0,0 +1,101 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Infrastructure.States;
using Xunit;
namespace Squidex.Infrastructure.Orleans
{
public class GrainOfStringTests
{
private readonly IPersistence<MyGrain.GrainState> persistence = A.Fake<IPersistence<MyGrain.GrainState>>();
private readonly IStore<string> store = A.Fake<IStore<string>>();
private readonly string id = Guid.NewGuid().ToString();
private readonly MyGrain sut;
private HandleSnapshot<MyGrain.GrainState> read;
public sealed class MyGrain : GrainOfString<MyGrain.GrainState>
{
public sealed class GrainState
{
public string Id { get; set; }
}
public GrainState PublicState
{
get { return State; }
}
public MyGrain(IStore<string> store)
: base(store)
{
}
public Task PublicWriteAsync()
{
return WriteStateAsync();
}
public Task PublicClearAsync()
{
return ClearStateAsync();
}
}
public GrainOfStringTests()
{
A.CallTo(() => persistence.ReadAsync(EtagVersion.Any))
.Invokes(_ =>
{
read(new MyGrain.GrainState { Id = id });
});
A.CallTo(() => store.WithSnapshots(typeof(MyGrain), id, A<HandleSnapshot<MyGrain.GrainState>>.Ignored))
.Invokes(new Action<Type, string, HandleSnapshot<MyGrain.GrainState>>((type, id, callback) =>
{
read = callback;
}))
.Returns(persistence);
sut = new MyGrain(store);
}
[Fact]
public async Task Should_read_on_activate()
{
await sut.ActivateAsync(id);
Assert.Equal(id, sut.PublicState.Id);
A.CallTo(() => persistence.ReadAsync(EtagVersion.Any))
.MustHaveHappened();
}
[Fact]
public async Task Should_invoke_persistence_on_write()
{
await sut.ActivateAsync(id);
await sut.PublicWriteAsync();
A.CallTo(() => persistence.WriteSnapshotAsync(sut.PublicState))
.MustHaveHappened();
}
[Fact]
public async Task Should_invoke_persistence_on_clear()
{
await sut.ActivateAsync(id);
await sut.PublicClearAsync();
A.CallTo(() => persistence.DeleteAsync())
.MustHaveHappened();
}
}
}

45
tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs

@ -22,16 +22,19 @@ namespace Squidex.Infrastructure.States
private readonly IEventDataFormatter eventDataFormatter = A.Fake<IEventDataFormatter>(); private readonly IEventDataFormatter eventDataFormatter = A.Fake<IEventDataFormatter>();
private readonly IEventStore eventStore = A.Fake<IEventStore>(); private readonly IEventStore eventStore = A.Fake<IEventStore>();
private readonly IServiceProvider services = A.Fake<IServiceProvider>(); private readonly IServiceProvider services = A.Fake<IServiceProvider>();
private readonly ISnapshotStore<object, string> snapshotStore = A.Fake<ISnapshotStore<object, string>>(); private readonly ISnapshotStore<int, string> snapshotStore = A.Fake<ISnapshotStore<int, string>>();
private readonly ISnapshotStore<None, string> snapshotStoreNone = A.Fake<ISnapshotStore<None, string>>();
private readonly IStreamNameResolver streamNameResolver = A.Fake<IStreamNameResolver>(); private readonly IStreamNameResolver streamNameResolver = A.Fake<IStreamNameResolver>();
private readonly IStore<string> sut; private readonly IStore<string> sut;
public PersistenceEventSourcingTests() public PersistenceEventSourcingTests()
{ {
A.CallTo(() => services.GetService(typeof(ISnapshotStore<object, string>))) A.CallTo(() => services.GetService(typeof(ISnapshotStore<int, string>)))
.Returns(snapshotStore); .Returns(snapshotStore);
A.CallTo(() => services.GetService(typeof(ISnapshotStore<None, string>)))
.Returns(snapshotStoreNone);
A.CallTo(() => streamNameResolver.GetStreamName(typeof(object), key)) A.CallTo(() => streamNameResolver.GetStreamName(None.Type, key))
.Returns(key); .Returns(key);
sut = new Store<string>(eventStore, eventDataFormatter, services, streamNameResolver); sut = new Store<string>(eventStore, eventDataFormatter, services, streamNameResolver);
@ -46,7 +49,7 @@ namespace Squidex.Infrastructure.States
SetupEventStore(event1, event2); SetupEventStore(event1, event2);
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithEventSourcing<object, string>(key, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithEventSourcing(None.Type, key, x => persistedEvents.Add(x.Payload));
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -65,7 +68,7 @@ namespace Squidex.Infrastructure.States
.Throws(new TypeNameNotFoundException()); .Throws(new TypeNameNotFoundException());
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithEventSourcing<object, string>(key, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithEventSourcing(None.Type, key, x => persistedEvents.Add(x.Payload));
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -81,9 +84,9 @@ namespace Squidex.Infrastructure.States
SetupEventStore(3, 2); SetupEventStore(3, 2);
var persistedState = (object)null; var persistedState = -1;
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithSnapshotsAndEventSourcing<object, object, string>(key, x => persistedState = x, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithSnapshotsAndEventSourcing(None.Type, key, (int x) => persistedState = x, x => persistedEvents.Add(x.Payload));
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -99,9 +102,9 @@ namespace Squidex.Infrastructure.States
SetupEventStore(3, 0, 3); SetupEventStore(3, 0, 3);
var persistedState = (object)null; var persistedState = -1;
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithSnapshotsAndEventSourcing<object, object, string>(key, x => persistedState = x, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithSnapshotsAndEventSourcing(None.Type, key, (int x) => persistedState = x, x => persistedEvents.Add(x.Payload));
await Assert.ThrowsAsync<InvalidOperationException>(() => persistence.ReadAsync()); await Assert.ThrowsAsync<InvalidOperationException>(() => persistence.ReadAsync());
} }
@ -114,9 +117,9 @@ namespace Squidex.Infrastructure.States
SetupEventStore(3, 4, 3); SetupEventStore(3, 4, 3);
var persistedState = (object)null; var persistedState = -1;
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithSnapshotsAndEventSourcing<object, object, string>(key, x => persistedState = x, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithSnapshotsAndEventSourcing(None.Type, key, (int x) => persistedState = x, x => persistedEvents.Add(x.Payload));
await Assert.ThrowsAsync<InvalidOperationException>(() => persistence.ReadAsync()); await Assert.ThrowsAsync<InvalidOperationException>(() => persistence.ReadAsync());
} }
@ -127,7 +130,7 @@ namespace Squidex.Infrastructure.States
SetupEventStore(0); SetupEventStore(0);
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithEventSourcing<object, string>(key, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithEventSourcing(None.Type, key, x => persistedEvents.Add(x.Payload));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => persistence.ReadAsync(1)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => persistence.ReadAsync(1));
} }
@ -138,7 +141,7 @@ namespace Squidex.Infrastructure.States
SetupEventStore(3); SetupEventStore(3);
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithEventSourcing<object, string>(key, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithEventSourcing(None.Type, key, x => persistedEvents.Add(x.Payload));
await Assert.ThrowsAsync<DomainObjectVersionException>(() => persistence.ReadAsync(1)); await Assert.ThrowsAsync<DomainObjectVersionException>(() => persistence.ReadAsync(1));
} }
@ -151,9 +154,9 @@ namespace Squidex.Infrastructure.States
SetupEventStore(0); SetupEventStore(0);
var persistedState = (object)null; var persistedState = -1;
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithSnapshotsAndEventSourcing<object, object, string>(key, x => persistedState = x, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithSnapshotsAndEventSourcing(None.Type, key, (int x) => persistedState = x, x => persistedEvents.Add(x.Payload));
await Assert.ThrowsAsync<DomainObjectVersionException>(() => persistence.ReadAsync(1)); await Assert.ThrowsAsync<DomainObjectVersionException>(() => persistence.ReadAsync(1));
} }
@ -163,9 +166,9 @@ namespace Squidex.Infrastructure.States
{ {
SetupEventStore(0); SetupEventStore(0);
var persistedState = (object)null; var persistedState = -1;
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithSnapshotsAndEventSourcing<object, object, string>(key, x => persistedState = x, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithSnapshotsAndEventSourcing(None.Type, key, (int x) => persistedState = x, x => persistedEvents.Add(x.Payload));
await persistence.ReadAsync(); await persistence.ReadAsync();
} }
@ -176,7 +179,7 @@ namespace Squidex.Infrastructure.States
SetupEventStore(3); SetupEventStore(3);
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithEventSourcing<object, string>(key, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithEventSourcing(None.Type, key, x => persistedEvents.Add(x.Payload));
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -195,7 +198,7 @@ namespace Squidex.Infrastructure.States
SetupEventStore(3); SetupEventStore(3);
var persistedEvents = new List<IEvent>(); var persistedEvents = new List<IEvent>();
var persistence = sut.WithEventSourcing<object, string>(key, x => persistedEvents.Add(x.Payload)); var persistence = sut.WithEventSourcing(None.Type, key, x => persistedEvents.Add(x.Payload));
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -208,7 +211,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_delete_events_but_not_snapshot_when_deleted_snapshot_only() public async Task Should_delete_events_but_not_snapshot_when_deleted_snapshot_only()
{ {
var persistence = sut.WithEventSourcing<object, string>(key, x => { }); var persistence = sut.WithEventSourcing(None.Type, key, null);
await persistence.DeleteAsync(); await persistence.DeleteAsync();
@ -222,7 +225,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_delete_events_and_snapshot_when_deleted() public async Task Should_delete_events_and_snapshot_when_deleted()
{ {
var persistence = sut.WithSnapshotsAndEventSourcing<object, object, string>(key, x => { }, x => { }); var persistence = sut.WithSnapshotsAndEventSourcing<int>(None.Type, key, null, null);
await persistence.DeleteAsync(); await persistence.DeleteAsync();

17
tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs

@ -40,7 +40,7 @@ namespace Squidex.Infrastructure.States
.Returns((20, 10)); .Returns((20, 10));
var persistedState = 0; var persistedState = 0;
var persistence = sut.WithSnapshots<object, int, string>(key, x => persistedState = x); var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -55,7 +55,7 @@ namespace Squidex.Infrastructure.States
.Returns((20, -10)); .Returns((20, -10));
var persistedState = 0; var persistedState = 0;
var persistence = sut.WithSnapshots<object, int, string>(key, x => persistedState = x); var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -69,7 +69,7 @@ namespace Squidex.Infrastructure.States
.Returns((20, EtagVersion.Empty)); .Returns((20, EtagVersion.Empty));
var persistedState = 0; var persistedState = 0;
var persistence = sut.WithSnapshots<object, int, string>(key, x => persistedState = x); var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -84,7 +84,7 @@ namespace Squidex.Infrastructure.States
.Returns((123, EtagVersion.Empty)); .Returns((123, EtagVersion.Empty));
var persistedState = 0; var persistedState = 0;
var persistence = sut.WithSnapshots<object, int, string>(key, x => persistedState = x); var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => persistence.ReadAsync(1)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => persistence.ReadAsync(1));
} }
@ -96,7 +96,7 @@ namespace Squidex.Infrastructure.States
.Returns((123, 2)); .Returns((123, 2));
var persistedState = 0; var persistedState = 0;
var persistence = sut.WithSnapshots<object, int, string>(key, x => persistedState = x); var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await Assert.ThrowsAsync<DomainObjectVersionException>(() => persistence.ReadAsync(1)); await Assert.ThrowsAsync<DomainObjectVersionException>(() => persistence.ReadAsync(1));
} }
@ -108,7 +108,7 @@ namespace Squidex.Infrastructure.States
.Returns((20, 10)); .Returns((20, 10));
var persistedState = 0; var persistedState = 0;
var persistence = sut.WithSnapshots<object, int, string>(key, x => persistedState = x); var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -131,7 +131,7 @@ namespace Squidex.Infrastructure.States
.Throws(new InconsistentStateException(1, 1, new InvalidOperationException())); .Throws(new InconsistentStateException(1, 1, new InvalidOperationException()));
var persistedState = 0; var persistedState = 0;
var persistence = sut.WithSnapshots<object, int, string>(key, x => persistedState = x); var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await persistence.ReadAsync(); await persistence.ReadAsync();
@ -141,7 +141,8 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_delete_snapshot_but_not_events_when_deleted() public async Task Should_delete_snapshot_but_not_events_when_deleted()
{ {
var persistence = sut.WithSnapshots<object, int, string>(key, x => { }); var persistedState = 0;
var persistence = sut.WithSnapshots(None.Type, key, (int x) => persistedState = x);
await persistence.DeleteAsync(); await persistence.DeleteAsync();

2
tools/Migrate_01/Rebuilder.cs

@ -126,7 +126,7 @@ namespace Migrate_01
Version = EtagVersion.Empty Version = EtagVersion.Empty
}; };
var persistence = store.WithSnapshotsAndEventSourcing<TState, Guid>(typeof(TGrain), key, s => state = s, e => var persistence = store.WithSnapshotsAndEventSourcing(typeof(TGrain), key, (TState s) => state = s, e =>
{ {
state = func(e, state); state = func(e, state);

Loading…
Cancel
Save