Browse Source

Solve deadlock in rules.

pull/283/head
Sebastian 8 years ago
parent
commit
a699cdcf07
  1. 3
      src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs
  2. 16
      src/Squidex.Domain.Apps.Core.Model/Rules/RuleJobData.cs
  3. 87
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs
  4. 52
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs
  5. 108
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs
  6. 58
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs
  7. 66
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs
  8. 67
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  9. 5
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs
  10. 22
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  11. 16
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  12. 7
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  13. 2
      src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs
  14. 1
      src/Squidex.Infrastructure/CachingProviderBase.cs
  15. 1
      src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs
  16. 1
      src/Squidex/Config/Orleans/SiloWrapper.cs
  17. 1
      src/Squidex/Program.cs
  18. 86
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  19. 41
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  20. 5
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs

3
src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
@ -28,6 +29,6 @@ namespace Squidex.Domain.Apps.Core.Rules
public Instant Expires { get; set; } public Instant Expires { get; set; }
public RuleJobData ActionData { get; set; } public JObject ActionData { get; set; }
} }
} }

16
src/Squidex.Domain.Apps.Core.Model/Rules/RuleJobData.cs

@ -1,16 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Core.Rules
{
public sealed class RuleJobData : Dictionary<string, JToken>
{
}
}

87
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs

@ -11,17 +11,32 @@ using Algolia.Search;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction> public sealed class AlgoliaJob
{ {
public string AppId { get; set; }
public string ApiKey { get; set; }
public string ContentId { get; set; }
public string IndexName { get; set; }
public JObject Content { get; set; }
}
public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, AlgoliaJob>
{
private const string DescriptionIgnore = "Ignore";
private readonly ClientPool<(string AppId, string ApiKey, string IndexName), Index> clients; private readonly ClientPool<(string AppId, string ApiKey, string IndexName), Index> clients;
private readonly RuleEventFormatter formatter; private readonly RuleEventFormatter formatter;
@ -39,20 +54,20 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
}); });
} }
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, AlgoliaAction action) protected override async Task<(string Description, AlgoliaJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, AlgoliaAction action)
{ {
var ruleDescription = string.Empty; if (@event.Payload is ContentEvent contentEvent)
var ruleData = new RuleJobData
{ {
["AppId"] = action.AppId, var contentId = contentEvent.ContentId.ToString();
["ApiKey"] = action.ApiKey
};
if (@event.Payload is ContentEvent contentEvent) var ruleDescription = string.Empty;
var ruleJob = new AlgoliaJob
{ {
ruleData["ContentId"] = contentEvent.ContentId.ToString(); AppId = action.AppId,
ruleData["Operation"] = "Upsert"; ApiKey = action.ApiKey,
ruleData["IndexName"] = formatter.FormatString(action.IndexName, @event); ContentId = contentId,
IndexName = await formatter.FormatStringAsync(action.IndexName, @event)
};
var timestamp = @event.Headers.Timestamp().ToString(); var timestamp = @event.Headers.Timestamp().ToString();
@ -62,8 +77,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
ruleDescription = $"Add entry to Algolia index: {action.IndexName}"; ruleDescription = $"Add entry to Algolia index: {action.IndexName}";
ruleData["Content"] = new JObject( ruleJob.Content = new JObject(
new JProperty("id", contentEvent.ContentId), new JProperty("objectID", contentId),
new JProperty("id", contentId),
new JProperty("created", timestamp), new JProperty("created", timestamp),
new JProperty("createdBy", created.Actor.ToString()), new JProperty("createdBy", created.Actor.ToString()),
new JProperty("lastModified", timestamp), new JProperty("lastModified", timestamp),
@ -77,7 +93,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
ruleDescription = $"Update entry in Algolia index: {action.IndexName}"; ruleDescription = $"Update entry in Algolia index: {action.IndexName}";
ruleData["Content"] = new JObject( ruleJob.Content = new JObject(
new JProperty("objectID", contentId),
new JProperty("lastModified", timestamp), new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", updated.Actor.ToString()), new JProperty("lastModifiedBy", updated.Actor.ToString()),
new JProperty("data", formatter.ToRouteData(updated.Data))); new JProperty("data", formatter.ToRouteData(updated.Data)));
@ -88,7 +105,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
ruleDescription = $"Update entry in Algolia index: {action.IndexName}"; ruleDescription = $"Update entry in Algolia index: {action.IndexName}";
ruleData["Content"] = new JObject( ruleJob.Content = new JObject(
new JProperty("objectID", contentId),
new JProperty("lastModified", timestamp), new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", statusChanged.Actor.ToString()), new JProperty("lastModifiedBy", statusChanged.Actor.ToString()),
new JProperty("status", statusChanged.Status.ToString())); new JProperty("status", statusChanged.Status.ToString()));
@ -99,56 +117,39 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
ruleDescription = $"Delete entry from Algolia index: {action.IndexName}"; ruleDescription = $"Delete entry from Algolia index: {action.IndexName}";
ruleData["Content"] = new JObject();
ruleData["Operation"] = "Delete";
break; break;
} }
} }
return (ruleDescription, ruleJob);
} }
return (ruleDescription, ruleData); return (DescriptionIgnore, new AlgoliaJob());
} }
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(AlgoliaJob job)
{ {
if (!job.TryGetValue("Operation", out var operationToken)) if (string.IsNullOrWhiteSpace(job.AppId))
{ {
return (null, new InvalidOperationException("The action cannot handle this event.")); return (null, new InvalidOperationException("The action cannot handle this event."));
} }
var appId = job["AppId"].Value<string>(); var index = clients.GetClient((job.AppId, job.ApiKey, job.IndexName));
var apiKey = job["ApiKey"].Value<string>();
var indexName = job["IndexName"].Value<string>();
var index = clients.GetClient((appId, apiKey, indexName));
var operation = operationToken.Value<string>();
var content = job["Content"].Value<JObject>();
var contentId = job["ContentId"].Value<string>();
try try
{ {
switch (operation) if (job.Content != null)
{ {
case "Upsert": var response = await index.PartialUpdateObjectAsync(job.Content);
{
content["objectID"] = contentId;
var response = await index.PartialUpdateObjectAsync(content);
return (response.ToString(Formatting.Indented), null); return (response.ToString(Formatting.Indented), null);
} }
else
case "Delete":
{ {
var response = await index.DeleteObjectAsync(contentId); var response = await index.DeleteObjectAsync(job.ContentId);
return (response.ToString(Formatting.Indented), null); return (response.ToString(Formatting.Indented), null);
} }
default:
return (null, null);
}
} }
catch (AlgoliaException ex) catch (AlgoliaException ex)
{ {

52
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs

@ -11,15 +11,34 @@ using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue; using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
public sealed class AzureQueueActionHandler : RuleActionHandler<AzureQueueAction> public sealed class AzureQueueJob
{
public string QueueConnectionString { get; set; }
public string QueueName { get; set; }
public string MessageBodyV2 { get; set; }
public JObject MessageBody { get; set; }
public string Body
{
get
{
return MessageBodyV2 ?? MessageBody.ToString(Formatting.Indented);
}
}
}
public sealed class AzureQueueActionHandler : RuleActionHandler<AzureQueueAction, AzureQueueJob>
{ {
private readonly ClientPool<(string ConnectionString, string QueueName), CloudQueue> clients; private readonly ClientPool<(string ConnectionString, string QueueName), CloudQueue> clients;
private readonly RuleEventFormatter formatter; private readonly RuleEventFormatter formatter;
@ -41,31 +60,28 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
}); });
} }
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, AzureQueueAction action) protected override async Task<(string Description, AzureQueueJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, AzureQueueAction action)
{ {
var body = formatter.ToRouteData(@event, eventName); var body = formatter.ToRouteData(@event, eventName).ToString(Formatting.Indented);
var ruleDescription = $"Send event to azure queue '{action.Queue}'"; var queueName = await formatter.FormatStringAsync(action.Queue, @event);
var ruleData = new RuleJobData
var ruleDescription = $"Send AzureQueueJob to azure queue '{action.Queue}'";
var ruleJob = new AzureQueueJob
{ {
["QueueConnectionString"] = action.ConnectionString, QueueConnectionString = action.ConnectionString,
["QueueName"] = action.Queue, QueueName = queueName,
["MessageBody"] = body MessageBodyV2 = body,
}; };
return (ruleDescription, ruleData); return (ruleDescription, ruleJob);
} }
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(AzureQueueJob job)
{ {
var queueConnectionString = job["QueueConnectionString"].Value<string>(); var queue = clients.GetClient((job.QueueConnectionString, job.QueueName));
var queueName = job["QueueName"].Value<string>();
var queue = clients.GetClient((queueConnectionString, queueName));
var messageBody = job["MessageBody"].ToString(Formatting.Indented);
await queue.AddMessageAsync(new CloudQueueMessage(messageBody)); await queue.AddMessageAsync(new CloudQueueMessage(job.Body));
return ("Completed", null); return ("Completed", null);
} }

108
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs

@ -10,17 +10,37 @@ using System.Threading.Tasks;
using Elasticsearch.Net; using Elasticsearch.Net;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearchAction> public sealed class ElasticSearchJob
{ {
public string Host { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string ContentId { get; set; }
public string IndexName { get; set; }
public string IndexType { get; set; }
public string Operation { get; set; }
public JObject Content { get; set; }
}
public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearchAction, ElasticSearchJob>
{
private const string DescriptionIgnore = "Ignore";
private readonly ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient> clients; private readonly ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient> clients;
private readonly RuleEventFormatter formatter; private readonly RuleEventFormatter formatter;
@ -43,37 +63,40 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
}); });
} }
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, ElasticSearchAction action) protected override async Task<(string Description, ElasticSearchJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, ElasticSearchAction action)
{
var ruleDescription = string.Empty;
var ruleData = new RuleJobData
{ {
["Host"] = action.Host,
["Username"] = action.Username,
["Password"] = action.Password
};
if (@event.Payload is ContentEvent contentEvent) if (@event.Payload is ContentEvent contentEvent)
{ {
ruleData["ContentId"] = contentEvent.ContentId.ToString(); var contentId = contentEvent.ContentId.ToString();
ruleData["IndexName"] = formatter.FormatString(action.IndexName, @event);
ruleData["IndexType"] = formatter.FormatString(action.IndexType, @event); var ruleDescription = string.Empty;
var ruleJob = new ElasticSearchJob
{
Host = action.Host.ToString(),
Username = action.Username,
Password = action.Password,
ContentId = contentId,
IndexName = await formatter.FormatStringAsync(action.IndexName, @event),
IndexType = await formatter.FormatStringAsync(action.IndexType, @event),
};
var timestamp = @event.Headers.Timestamp().ToString(); var timestamp = @event.Headers.Timestamp().ToString();
var actor = @event.Payload.Actor.ToString();
switch (@event.Payload) switch (@event.Payload)
{ {
case ContentCreated created: case ContentCreated created:
{ {
ruleDescription = $"Add entry to ES index: {action.IndexName}"; ruleDescription = $"Add entry to ES index: {action.IndexName}";
ruleData["Operation"] = "Create"; ruleJob.Operation = "Create";
ruleData["Content"] = new JObject( ruleJob.Content = new JObject(
new JProperty("id", contentEvent.ContentId), new JProperty("id", contentId),
new JProperty("created", timestamp), new JProperty("created", timestamp),
new JProperty("createdBy", created.Actor.ToString()), new JProperty("createdBy", actor),
new JProperty("lastModified", timestamp), new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", created.Actor.ToString()), new JProperty("lastModifiedBy", actor),
new JProperty("status", Status.Draft.ToString()), new JProperty("status", Status.Draft.ToString()),
new JProperty("data", formatter.ToRouteData(created.Data))); new JProperty("data", formatter.ToRouteData(created.Data)));
break; break;
@ -83,10 +106,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
ruleDescription = $"Update entry in ES index: {action.IndexName}"; ruleDescription = $"Update entry in ES index: {action.IndexName}";
ruleData["Operation"] = "Update"; ruleJob.Operation = "Update";
ruleData["Content"] = new JObject( ruleJob.Content = new JObject(
new JProperty("lastModified", timestamp), new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", updated.Actor.ToString()), new JProperty("lastModifiedBy", actor),
new JProperty("data", formatter.ToRouteData(updated.Data))); new JProperty("data", formatter.ToRouteData(updated.Data)));
break; break;
} }
@ -95,10 +118,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
ruleDescription = $"Update entry in ES index: {action.IndexName}"; ruleDescription = $"Update entry in ES index: {action.IndexName}";
ruleData["Operation"] = "Update"; ruleJob.Operation = "Update";
ruleData["Content"] = new JObject( ruleJob.Content = new JObject(
new JProperty("lastModified", timestamp), new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", statusChanged.Actor.ToString()), new JProperty("lastModifiedBy", actor),
new JProperty("status", statusChanged.Status.ToString())); new JProperty("status", statusChanged.Status.ToString()));
break; break;
} }
@ -107,62 +130,49 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
ruleDescription = $"Delete entry from ES index: {action.IndexName}"; ruleDescription = $"Delete entry from ES index: {action.IndexName}";
ruleData["Operation"] = "Delete"; ruleJob.Operation = "Delete";
ruleData["Content"] = new JObject();
break; break;
} }
} }
} }
return (ruleDescription, ruleData); return (DescriptionIgnore, new ElasticSearchJob());
} }
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(ElasticSearchJob job)
{ {
if (!job.TryGetValue("Operation", out var operationToken)) if (string.IsNullOrWhiteSpace(job.Operation))
{ {
return (null, new InvalidOperationException("The action cannot handle this event.")); return (null, new InvalidOperationException("The action cannot handle this event."));
} }
var host = new Uri(job["Host"].Value<string>(), UriKind.Absolute); var client = clients.GetClient((new Uri(job.Host, UriKind.Absolute), job.Username, job.Password));
var username = job["Username"].Value<string>();
var password = job["Password"].Value<string>();
var client = clients.GetClient((host, username, password));
var indexName = job["IndexName"].Value<string>();
var indexType = job["IndexType"].Value<string>();
var operation = operationToken.Value<string>();
var content = job["Content"].Value<JObject>();
var contentId = job["ContentId"].Value<string>();
try try
{ {
switch (operation) switch (job.Operation)
{ {
case "Create": case "Create":
{ {
var doc = content.ToString(); var doc = job.Content.ToString();
var response = await client.IndexAsync<StringResponse>(indexName, indexType, contentId, doc); var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc);
return (response.Body, response.OriginalException); return (response.Body, response.OriginalException);
} }
case "Update": case "Update":
{ {
var doc = new JObject(new JProperty("doc", content)).ToString(); var doc = new JObject(new JProperty("doc", job.Content)).ToString();
var response = await client.UpdateAsync<StringResponse>(indexName, indexType, contentId, doc); var response = await client.UpdateAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc);
return (response.Body, response.OriginalException); return (response.Body, response.OriginalException);
} }
case "Delete": case "Delete":
{ {
var response = await client.DeleteAsync<StringResponse>(indexName, indexType, contentId); var response = await client.DeleteAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId);
return (response.Body, response.OriginalException); return (response.Body, response.OriginalException);
} }

58
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs

@ -6,45 +6,54 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
public sealed class FastlyActionHandler : RuleActionHandler<FastlyAction> public sealed class FastlyJob
{
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, FastlyAction action)
{ {
var ruleDescription = "Purge key in fastly"; public string Key { get; set; }
var ruleData = new RuleJobData public string FastlyApiKey { get; set; }
public string FastlyServiceID { get; set; }
}
public sealed class FastlyActionHandler : RuleActionHandler<FastlyAction, FastlyJob>
{ {
["FastlyApiKey"] = action.ApiKey, private const string Description = "Purge key in fastly";
["FastlyServiceID"] = action.ServiceId private const string DescriptionIgnore = "Ignore";
};
protected override Task<(string Description, FastlyJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, FastlyAction action)
{
if (@event.Headers.Contains(CommonHeaders.AggregateId)) if (@event.Headers.Contains(CommonHeaders.AggregateId))
{ {
ruleData["Key"] = @event.Headers.AggregateId().ToString(); var ruleData = new FastlyJob
{
Key = @event.Headers.AggregateId().ToString(),
FastlyApiKey = action.ApiKey,
FastlyServiceID = action.ServiceId
};
return Task.FromResult((Description, ruleData));
} }
return (ruleDescription, ruleData); return Task.FromResult((DescriptionIgnore, new FastlyJob()));
} }
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job)
{ {
if (!job.TryGetValue("Key", out var keyToken)) if (string.IsNullOrWhiteSpace(job.Key))
{ {
return (null, new InvalidOperationException("The action cannot handle this event.")); return (null, new InvalidOperationException("The action cannot handle this event."));
} }
var requestMsg = BuildRequest(job, keyToken.Value<string>()); var requestMsg = BuildRequest(job);
HttpResponseMessage response = null; HttpResponseMessage response = null;
@ -58,30 +67,19 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
return (requestDump, null); return (requestDump, null);
} }
catch (Exception ex) catch (Exception ex)
{
if (requestMsg != null)
{ {
var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, ex.ToString(), TimeSpan.Zero, false); var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, ex.ToString(), TimeSpan.Zero, false);
return (requestDump, ex);
}
else
{
var requestDump = ex.ToString();
return (requestDump, ex); return (requestDump, ex);
} }
} }
}
private static HttpRequestMessage BuildRequest(Dictionary<string, JToken> job, string key) private static HttpRequestMessage BuildRequest(FastlyJob job)
{ {
var serviceId = job["FastlyServiceID"].Value<string>(); var requestUrl = $"https://api.fastly.com/service/{job.FastlyServiceID}/purge/{job.Key}";
var requestUrl = $"https://api.fastly.com/service/{serviceId}/purge/{key}";
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl); var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Headers.Add("Fastly-Key", job["FastlyApiKey"].Value<string>()); request.Headers.Add("Fastly-Key", job.FastlyApiKey);
return request; return request;
} }

66
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs

@ -6,23 +6,41 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
public sealed class SlackActionHandler : RuleActionHandler<SlackAction> public sealed class SlackJob
{
public string RequestUrl { get; set; }
public string RequestBodyV2 { get; set; }
public JObject RequestBody { get; set; }
public string Body
{
get
{
return RequestBodyV2 ?? RequestBody.ToString(Formatting.Indented);
}
}
}
public sealed class SlackActionHandler : RuleActionHandler<SlackAction, SlackJob>
{ {
private const string Description = "Send message to slack";
private readonly RuleEventFormatter formatter; private readonly RuleEventFormatter formatter;
public SlackActionHandler(RuleEventFormatter formatter) public SlackActionHandler(RuleEventFormatter formatter)
@ -32,61 +50,51 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; this.formatter = formatter;
} }
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, SlackAction action) protected override async Task<(string Description, SlackJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, SlackAction action)
{ {
var body = CreatePayload(@event, action.Text); var body = await CreatePayloadAsync(@event, action.Text);
var ruleDescription = "Send message to slack"; var ruleJob = new SlackJob
var ruleData = new RuleJobData
{ {
["RequestUrl"] = action.WebhookUrl, RequestUrl = action.WebhookUrl.ToString(),
["RequestBody"] = body RequestBodyV2 = body.ToString(Formatting.Indented),
}; };
return (ruleDescription, ruleData); return (Description, ruleJob);
} }
private JObject CreatePayload(Envelope<AppEvent> @event, string text) private async Task<JObject> CreatePayloadAsync(Envelope<AppEvent> @event, string text)
{ {
return new JObject(new JProperty("text", formatter.FormatString(text, @event))); return new JObject(new JProperty("text", await formatter.FormatStringAsync(text, @event)));
} }
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(SlackJob job)
{ {
var requestBody = job["RequestBody"].ToString(Formatting.Indented); var requestBody = job.Body;
var requestMsg = BuildRequest(job, requestBody); var requestMessage = BuildRequest(job, requestBody);
HttpResponseMessage response = null; HttpResponseMessage response = null;
try try
{ {
response = await HttpClientPool.GetHttpClient().SendAsync(requestMsg); response = await HttpClientPool.GetHttpClient().SendAsync(requestMessage);
var responseString = await response.Content.ReadAsStringAsync(); var responseString = await response.Content.ReadAsStringAsync();
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, responseString, TimeSpan.Zero, false); var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, responseString, TimeSpan.Zero, false);
return (requestDump, null); return (requestDump, null);
} }
catch (Exception ex) catch (Exception ex)
{ {
if (requestMsg != null) var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, ex.ToString(), TimeSpan.Zero, false);
{
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, ex.ToString(), TimeSpan.Zero, false);
return (requestDump, ex); return (requestDump, ex);
} }
else
{
throw;
}
}
} }
private static HttpRequestMessage BuildRequest(Dictionary<string, JToken> job, string requestBody) private static HttpRequestMessage BuildRequest(SlackJob job, string requestBody)
{ {
var requestUrl = job["RequestUrl"].Value<string>(); var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl)
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)
{ {
Content = new StringContent(requestBody, Encoding.UTF8, "application/json") Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
}; };

67
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs

@ -6,22 +6,39 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction> public sealed class WebhookJob
{
public string RequestUrl { get; set; }
public string RequestSignature { get; set; }
public string RequestBodyV2 { get; set; }
public JObject RequestBody { get; set; }
public string Body
{
get
{
return RequestBodyV2 ?? RequestBody.ToString(Formatting.Indented);
}
}
}
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction, WebhookJob>
{ {
private readonly RuleEventFormatter formatter; private readonly RuleEventFormatter formatter;
@ -32,36 +49,34 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; this.formatter = formatter;
} }
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, WebhookAction action) protected override async Task<(string Description, WebhookJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, WebhookAction action)
{ {
var body = formatter.ToRouteData(@event, eventName); var body = formatter.ToRouteData(@event, eventName).ToString(Formatting.Indented);
var signature = $"{body.ToString(Formatting.Indented)}{action.SharedSecret}".Sha256Base64();
var ruleDescription = $"Send event to webhook '{action.Url}'"; var ruleDescription = $"Send event to webhook '{action.Url}'";
var ruleData = new RuleJobData var ruleJob = new WebhookJob
{ {
["RequestUrl"] = action.Url, RequestUrl = await formatter.FormatStringAsync(action.Url.ToString(), @event),
["RequestBody"] = body, RequestSignature = $"{body}{action.SharedSecret}".Sha256Base64(),
["RequestSignature"] = signature RequestBodyV2 = body
}; };
return (ruleDescription, ruleData); return (ruleDescription, ruleJob);
} }
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(WebhookJob job)
{ {
var requestBody = job["RequestBody"].ToString(Formatting.Indented); var requestBody = job.Body;
var requestMsg = BuildRequest(job, requestBody); var requestMessage = BuildRequest(job, requestBody);
HttpResponseMessage response = null; HttpResponseMessage response = null;
try try
{ {
response = await HttpClientPool.GetHttpClient().SendAsync(requestMsg); response = await HttpClientPool.GetHttpClient().SendAsync(requestMessage);
var responseString = await response.Content.ReadAsStringAsync(); var responseString = await response.Content.ReadAsStringAsync();
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, responseString, TimeSpan.Zero, false); var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, responseString, TimeSpan.Zero, false);
Exception ex = null; Exception ex = null;
@ -74,30 +89,20 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
} }
catch (Exception ex) catch (Exception ex)
{ {
if (requestMsg != null) var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, ex.ToString(), TimeSpan.Zero, false);
{
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, ex.ToString(), TimeSpan.Zero, false);
return (requestDump, ex); return (requestDump, ex);
} }
else
{
throw;
}
}
} }
private static HttpRequestMessage BuildRequest(Dictionary<string, JToken> job, string requestBody) private static HttpRequestMessage BuildRequest(WebhookJob job, string requestBody)
{ {
var requestUrl = job["RequestUrl"].Value<string>(); var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl)
var requestSig = job["RequestSignature"].Value<string>();
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)
{ {
Content = new StringContent(requestBody, Encoding.UTF8, "application/json") Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
}; };
request.Headers.Add("X-Signature", requestSig); request.Headers.Add("X-Signature", job.RequestSignature);
request.Headers.Add("User-Agent", "Squidex Webhook"); request.Headers.Add("User-Agent", "Squidex Webhook");
return request; return request;

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

@ -7,6 +7,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
@ -17,8 +18,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
Type ActionType { get; } Type ActionType { get; }
(string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, RuleAction action); Task<(string Description, JObject Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, RuleAction action);
Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData data); Task<(string Dump, Exception Exception)> ExecuteJobAsync(JObject data);
} }
} }

22
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs

@ -7,26 +7,36 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
{ {
public abstract class RuleActionHandler<T> : IRuleActionHandler where T : RuleAction public abstract class RuleActionHandler<TAction, TData> : IRuleActionHandler where TAction : RuleAction
{ {
Type IRuleActionHandler.ActionType Type IRuleActionHandler.ActionType
{ {
get { return typeof(T); } get { return typeof(TAction); }
} }
(string Description, RuleJobData Data) IRuleActionHandler.CreateJob(Envelope<AppEvent> @event, string eventName, RuleAction action) async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(Envelope<AppEvent> @event, string eventName, RuleAction action)
{ {
return CreateJob(@event, eventName, (T)action); var (description, data) = await CreateJobAsync(@event, eventName, (TAction)action);
return (description, JObject.FromObject(data));
}
async Task<(string Dump, Exception Exception)> IRuleActionHandler.ExecuteJobAsync(JObject data)
{
var typedData = data.ToObject<TData>();
return await ExecuteJobAsync(typedData);
} }
protected abstract (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, T action); protected abstract Task<(string Description, TData Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, TAction action);
public abstract Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job); protected abstract Task<(string Dump, Exception Exception)> ExecuteJobAsync(TData job);
} }
} }

16
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -9,6 +9,7 @@ using System;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -67,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
new JProperty("timestamp", @event.Headers.Timestamp().ToString())); new JProperty("timestamp", @event.Headers.Timestamp().ToString()));
} }
public virtual string FormatString(string text, Envelope<AppEvent> @event) public async virtual Task<string> FormatStringAsync(string text, Envelope<AppEvent> @event)
{ {
var sb = new StringBuilder(text); var sb = new StringBuilder(text);
@ -96,7 +97,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
sb.Replace(ContentUrlPlaceholder, urlGenerator.GenerateContentUIUrl(@event.Payload.AppId, contentEvent.SchemaId, contentEvent.ContentId)); sb.Replace(ContentUrlPlaceholder, urlGenerator.GenerateContentUIUrl(@event.Payload.AppId, contentEvent.SchemaId, contentEvent.ContentId));
} }
FormatUserInfo(@event, sb); await FormatUserInfoAsync(@event, sb);
FormatContentAction(@event, sb); FormatContentAction(@event, sb);
var result = sb.ToString(); var result = sb.ToString();
@ -114,7 +116,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return result; return result;
} }
private void FormatUserInfo(Envelope<AppEvent> @event, StringBuilder sb) private async Task FormatUserInfoAsync(Envelope<AppEvent> @event, StringBuilder sb)
{ {
var text = sb.ToString(); var text = sb.ToString();
@ -131,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
} }
else else
{ {
var user = FindUser(actor); var user = await FindUserAsync(actor);
if (user != null) if (user != null)
{ {
@ -222,17 +224,17 @@ namespace Squidex.Domain.Apps.Core.HandleRules
}); });
} }
private IUser FindUser(RefToken actor) private Task<IUser> FindUserAsync(RefToken actor)
{ {
var key = $"RuleEventFormatter_Users_${actor.Identifier}"; var key = $"RuleEventFormatter_Users_${actor.Identifier}";
return memoryCache.GetOrCreate(key, x => return memoryCache.GetOrCreateAsync(key, async x =>
{ {
x.AbsoluteExpirationRelativeToNow = UserCacheDuration; x.AbsoluteExpirationRelativeToNow = UserCacheDuration;
try try
{ {
return userResolver.FindByIdOrEmailAsync(actor.Identifier).Result; return await userResolver.FindByIdOrEmailAsync(actor.Identifier);
} }
catch catch
{ {

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

@ -11,6 +11,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
@ -46,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
this.clock = clock; this.clock = clock;
} }
public virtual RuleJob CreateJob(Rule rule, Envelope<IEvent> @event) public virtual async Task<RuleJob> CreateJobAsync(Rule rule, Envelope<IEvent> @event)
{ {
Guard.NotNull(rule, nameof(rule)); Guard.NotNull(rule, nameof(rule));
Guard.NotNull(@event, nameof(@event)); Guard.NotNull(@event, nameof(@event));
@ -80,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var now = clock.GetCurrentInstant(); var now = clock.GetCurrentInstant();
var actionName = typeNameRegistry.GetName(actionType); var actionName = typeNameRegistry.GetName(actionType);
var actionData = actionHandler.CreateJob(appEventEnvelope, eventName, rule.Action); var actionData = await actionHandler.CreateJobAsync(appEventEnvelope, eventName, rule.Action);
var eventTime = var eventTime =
@event.Headers.Contains(CommonHeaders.Timestamp) ? @event.Headers.Contains(CommonHeaders.Timestamp) ?
@ -113,7 +114,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return job; return job;
} }
public virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, RuleJobData job) public virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, JObject job)
{ {
try try
{ {

2
src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs

@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
foreach (var ruleEntity in rules) foreach (var ruleEntity in rules)
{ {
var job = ruleService.CreateJob(ruleEntity.RuleDef, @event); var job = await ruleService.CreateJobAsync(ruleEntity.RuleDef, @event);
if (job != null) if (job != null)
{ {

1
src/Squidex.Infrastructure/CachingProviderBase.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Squidex.Infrastructure;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {

1
src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Users;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Shared.Users; using Squidex.Shared.Users;

1
src/Squidex/Config/Orleans/SiloWrapper.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;

1
src/Squidex/Program.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;

86
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

@ -79,88 +79,88 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
} }
[Fact] [Fact]
public void Should_replace_app_information_from_event() public async Task Should_replace_app_information_from_event()
{ {
var @event = new ContentCreated { AppId = appId }; var @event = new ContentCreated { AppId = appId };
var result = sut.FormatString("Name $APP_NAME has id $APP_ID", AsEnvelope(@event)); var result = await sut.FormatStringAsync("Name $APP_NAME has id $APP_ID", AsEnvelope(@event));
Assert.Equal($"Name my-app has id {appId.Id}", result); Assert.Equal($"Name my-app has id {appId.Id}", result);
} }
[Fact] [Fact]
public void Should_replace_schema_information_from_event() public async Task Should_replace_schema_information_from_event()
{ {
var @event = new ContentCreated { SchemaId = schemaId }; var @event = new ContentCreated { SchemaId = schemaId };
var result = sut.FormatString("Name $SCHEMA_NAME has id $SCHEMA_ID", AsEnvelope(@event)); var result = await sut.FormatStringAsync("Name $SCHEMA_NAME has id $SCHEMA_ID", AsEnvelope(@event));
Assert.Equal($"Name my-schema has id {schemaId.Id}", result); Assert.Equal($"Name my-schema has id {schemaId.Id}", result);
} }
[Fact] [Fact]
public void Should_replace_timestamp_information_from_event() public async Task Should_replace_timestamp_information_from_event()
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var envelope = AsEnvelope(new ContentCreated()).SetTimestamp(Instant.FromDateTimeUtc(now)); var envelope = AsEnvelope(new ContentCreated()).SetTimestamp(Instant.FromDateTimeUtc(now));
var result = sut.FormatString("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope); var result = await sut.FormatStringAsync("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope);
Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result); Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result);
} }
[Fact] [Fact]
public void Should_format_email_and_display_name_from_user() public async Task Should_format_email_and_display_name_from_user()
{ {
A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) A.CallTo(() => userResolver.FindByIdOrEmailAsync("123"))
.Returns(user); .Returns(user);
var @event = new ContentCreated { Actor = new RefToken("subject", "123") }; var @event = new ContentCreated { Actor = new RefToken("subject", "123") };
var result = sut.FormatString("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event));
Assert.Equal($"From me (me@email.com)", result); Assert.Equal($"From me (me@email.com)", result);
} }
[Fact] [Fact]
public void Should_return_undefined_if_user_is_not_found() public async Task Should_return_undefined_if_user_is_not_found()
{ {
A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) A.CallTo(() => userResolver.FindByIdOrEmailAsync("123"))
.Returns(Task.FromResult<IUser>(null)); .Returns(Task.FromResult<IUser>(null));
var @event = new ContentCreated { Actor = new RefToken("subject", "123") }; var @event = new ContentCreated { Actor = new RefToken("subject", "123") };
var result = sut.FormatString("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event));
Assert.Equal($"From UNDEFINED (UNDEFINED)", result); Assert.Equal($"From UNDEFINED (UNDEFINED)", result);
} }
[Fact] [Fact]
public void Should_return_undefined_if_user_failed_to_resolve() public async Task Should_return_undefined_if_user_failed_to_resolve()
{ {
A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) A.CallTo(() => userResolver.FindByIdOrEmailAsync("123"))
.Throws(new InvalidOperationException()); .Throws(new InvalidOperationException());
var @event = new ContentCreated { Actor = new RefToken("subject", "123") }; var @event = new ContentCreated { Actor = new RefToken("subject", "123") };
var result = sut.FormatString("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event));
Assert.Equal($"From UNDEFINED (UNDEFINED)", result); Assert.Equal($"From UNDEFINED (UNDEFINED)", result);
} }
[Fact] [Fact]
public void Should_format_email_and_display_name_from_client() public async Task Should_format_email_and_display_name_from_client()
{ {
var @event = new ContentCreated { Actor = new RefToken("client", "android") }; var @event = new ContentCreated { Actor = new RefToken("client", "android") };
var result = sut.FormatString("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event));
Assert.Equal($"From client:android (client:android)", result); Assert.Equal($"From client:android (client:android)", result);
} }
[Fact] [Fact]
public void Should_replacecontent_url_from_event() public async Task Should_replace_content_url_from_event()
{ {
var url = "http://content"; var url = "http://content";
@ -169,13 +169,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var @event = new ContentCreated { AppId = appId, ContentId = contentId, SchemaId = schemaId }; var @event = new ContentCreated { AppId = appId, ContentId = contentId, SchemaId = schemaId };
var result = sut.FormatString("Go to $CONTENT_URL", AsEnvelope(@event)); var result = await sut.FormatStringAsync("Go to $CONTENT_URL", AsEnvelope(@event));
Assert.Equal($"Go to {url}", result); Assert.Equal($"Go to {url}", result);
} }
[Fact] [Fact]
public void Should_return_undefined_when_field_not_found() public async Task Should_return_undefined_when_field_not_found()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -186,13 +186,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = sut.FormatString("$CONTENT_DATA.country.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.country.iv", AsEnvelope(@event));
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public void Should_return_undefined_when_partition_not_found() public async Task Should_return_undefined_when_partition_not_found()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -203,13 +203,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.de", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de", AsEnvelope(@event));
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public void Should_return_undefined_when_array_item_not_found() public async Task Should_return_undefined_when_array_item_not_found()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -220,13 +220,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", new JArray())) .AddValue("iv", new JArray()))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.de.10", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.10", AsEnvelope(@event));
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public void Should_return_undefined_when_property_not_found() public async Task Should_return_undefined_when_property_not_found()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -238,13 +238,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) new JProperty("name", "Berlin"))))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.de.Name", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.Name", AsEnvelope(@event));
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public void Should_return_plain_value_when_found() public async Task Should_return_plain_value_when_found()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -255,13 +255,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event));
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
[Fact] [Fact]
public void Should_return_plain_value_when_found_from_update_event() public async Task Should_return_plain_value_when_found_from_update_event()
{ {
var @event = new ContentUpdated var @event = new ContentUpdated
{ {
@ -272,13 +272,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event));
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
[Fact] [Fact]
public void Should_return_undefined_when_null() public async Task Should_return_undefined_when_null()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -289,13 +289,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JValue.CreateNull())) .AddValue("iv", JValue.CreateNull()))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event));
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public void Should_return_undefined_when_undefined() public async Task Should_return_undefined_when_undefined()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -306,13 +306,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JValue.CreateUndefined())) .AddValue("iv", JValue.CreateUndefined()))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event));
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public void Should_return_string_when_object() public async Task Should_return_string_when_object()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -324,13 +324,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) new JProperty("name", "Berlin"))))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event));
Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result); Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result);
} }
[Fact] [Fact]
public void Should_return_plain_value_from_array_when_found() public async Task Should_return_plain_value_from_array_when_found()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -342,13 +342,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
"Berlin"))) "Berlin")))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.iv.0", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.0", AsEnvelope(@event));
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
[Fact] [Fact]
public void Should_return_plain_value_from_object_when_found() public async Task Should_return_plain_value_from_object_when_found()
{ {
var @event = new ContentCreated var @event = new ContentCreated
{ {
@ -360,19 +360,19 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) new JProperty("name", "Berlin"))))
}; };
var result = sut.FormatString("$CONTENT_DATA.city.iv.name", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.name", AsEnvelope(@event));
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
[Fact] [Fact]
public void Should_format_content_actions_when_found() public async Task Should_format_content_actions_when_found()
{ {
Assert.Equal("created", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentCreated()))); Assert.Equal("created", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentCreated())));
Assert.Equal("updated", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentUpdated()))); Assert.Equal("updated", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentUpdated())));
Assert.Equal("deleted", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentDeleted()))); Assert.Equal("deleted", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentDeleted())));
Assert.Equal("set to archived", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentStatusChanged { Status = Status.Archived }))); Assert.Equal("set to archived", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentStatusChanged { Status = Status.Archived })));
} }
private static Envelope<AppEvent> AsEnvelope<T>(T @event) where T : AppEvent private static Envelope<AppEvent> AsEnvelope<T>(T @event) where T : AppEvent

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

@ -8,6 +8,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
@ -66,40 +67,40 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
} }
[Fact] [Fact]
public void Should_not_create_job_for_invalid_event() public async Task Should_not_create_job_for_invalid_event()
{ {
var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction()); var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction());
var ruleEnvelope = Envelope.Create(new InvalidEvent()); var ruleEnvelope = Envelope.Create(new InvalidEvent());
var job = sut.CreateJob(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
Assert.Null(job); Assert.Null(job);
} }
[Fact] [Fact]
public void Should_not_create_job_if_no_trigger_handler_registered() public async Task Should_not_create_job_if_no_trigger_handler_registered()
{ {
var ruleConfig = new Rule(new InvalidTrigger(), new WebhookAction()); var ruleConfig = new Rule(new InvalidTrigger(), new WebhookAction());
var ruleEnvelope = Envelope.Create(new ContentCreated()); var ruleEnvelope = Envelope.Create(new ContentCreated());
var job = sut.CreateJob(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
Assert.Null(job); Assert.Null(job);
} }
[Fact] [Fact]
public void Should_not_create_job_if_no_action_handler_registered() public async Task Should_not_create_job_if_no_action_handler_registered()
{ {
var ruleConfig = new Rule(new ContentChangedTrigger(), new InvalidAction()); var ruleConfig = new Rule(new ContentChangedTrigger(), new InvalidAction());
var ruleEnvelope = Envelope.Create(new ContentCreated()); var ruleEnvelope = Envelope.Create(new ContentCreated());
var job = sut.CreateJob(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
Assert.Null(job); Assert.Null(job);
} }
[Fact] [Fact]
public void Should_not_create_if_not_triggered() public async Task Should_not_create_if_not_triggered()
{ {
var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction()); var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction());
var ruleEnvelope = Envelope.Create(new ContentCreated()); var ruleEnvelope = Envelope.Create(new ContentCreated());
@ -107,13 +108,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger))
.Returns(false); .Returns(false);
var job = sut.CreateJob(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
Assert.Null(job); Assert.Null(job);
} }
[Fact] [Fact]
public void Should_not_create_job_if_too_old() public async Task Should_not_create_job_if_too_old()
{ {
var e = new ContentCreated { SchemaId = new NamedId<Guid>(Guid.NewGuid(), "my-schema"), AppId = new NamedId<Guid>(Guid.NewGuid(), "my-event") }; var e = new ContentCreated { SchemaId = new NamedId<Guid>(Guid.NewGuid(), "my-schema"), AppId = new NamedId<Guid>(Guid.NewGuid(), "my-event") };
@ -124,7 +125,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
ruleEnvelope.SetTimestamp(now.Minus(Duration.FromDays(3))); ruleEnvelope.SetTimestamp(now.Minus(Duration.FromDays(3)));
var actionData = new RuleJobData(); var actionData = new JObject();
var actionDescription = "MyDescription"; var actionDescription = "MyDescription";
var eventName = "MySchemaCreatedEvent"; var eventName = "MySchemaCreatedEvent";
@ -135,16 +136,16 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger))
.Returns(true); .Returns(true);
A.CallTo(() => ruleActionHandler.CreateJob(A<Envelope<AppEvent>>.Ignored, eventName, ruleConfig.Action)) A.CallTo(() => ruleActionHandler.CreateJobAsync(A<Envelope<AppEvent>>.Ignored, eventName, ruleConfig.Action))
.Returns((actionDescription, actionData)); .Returns((actionDescription, actionData));
var job = sut.CreateJob(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
Assert.Null(job); Assert.Null(job);
} }
[Fact] [Fact]
public void Should_create_job_if_triggered() public async Task Should_create_job_if_triggered()
{ {
var e = new ContentCreated { SchemaId = new NamedId<Guid>(Guid.NewGuid(), "my-schema"), AppId = new NamedId<Guid>(Guid.NewGuid(), "my-event") }; var e = new ContentCreated { SchemaId = new NamedId<Guid>(Guid.NewGuid(), "my-schema"), AppId = new NamedId<Guid>(Guid.NewGuid(), "my-event") };
@ -156,7 +157,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
ruleEnvelope.SetTimestamp(now); ruleEnvelope.SetTimestamp(now);
var actionName = "WebhookAction"; var actionName = "WebhookAction";
var actionData = new RuleJobData(); var actionData = new JObject();
var actionDescription = "MyDescription"; var actionDescription = "MyDescription";
var eventName = "MySchemaCreatedEvent"; var eventName = "MySchemaCreatedEvent";
@ -167,10 +168,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger))
.Returns(true); .Returns(true);
A.CallTo(() => ruleActionHandler.CreateJob(A<Envelope<AppEvent>>.Ignored, eventName, ruleConfig.Action)) A.CallTo(() => ruleActionHandler.CreateJobAsync(A<Envelope<AppEvent>>.Ignored, eventName, ruleConfig.Action))
.Returns((actionDescription, actionData)); .Returns((actionDescription, actionData));
var job = sut.CreateJob(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
Assert.Equal(eventName, job.EventName); Assert.Equal(eventName, job.EventName);
@ -189,7 +190,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_succeeded_job_with_full_dump_when_handler_returns_no_exception() public async Task Should_return_succeeded_job_with_full_dump_when_handler_returns_no_exception()
{ {
var ruleJob = new RuleJobData(); var ruleJob = new JObject();
var ruleEx = new InvalidOperationException(); var ruleEx = new InvalidOperationException();
var actionDump = "MyDump"; var actionDump = "MyDump";
@ -208,7 +209,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_failed_job_with_full_dump_when_handler_returns_exception() public async Task Should_return_failed_job_with_full_dump_when_handler_returns_exception()
{ {
var ruleJob = new RuleJobData(); var ruleJob = new JObject();
var ruleEx = new InvalidOperationException(); var ruleEx = new InvalidOperationException();
var actionDump = "MyDump"; var actionDump = "MyDump";
@ -227,7 +228,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_timedout_job_with_full_dump_when_exception_from_handler_indicates_timeout() public async Task Should_return_timedout_job_with_full_dump_when_exception_from_handler_indicates_timeout()
{ {
var ruleJob = new RuleJobData(); var ruleJob = new JObject();
var ruleEx = new InvalidOperationException(); var ruleEx = new InvalidOperationException();
var actionDump = "MyDump"; var actionDump = "MyDump";
@ -247,7 +248,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_create_exception_details_when_job_to_execute_failed() public async Task Should_create_exception_details_when_job_to_execute_failed()
{ {
var ruleJob = new RuleJobData(); var ruleJob = new JObject();
var ruleEx = new InvalidOperationException(); var ruleEx = new InvalidOperationException();
A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob)) A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob))

5
tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs

@ -8,6 +8,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
[InlineData(4, 0, RuleResult.Failed, RuleJobResult.Failed)] [InlineData(4, 0, RuleResult.Failed, RuleJobResult.Failed)]
public async Task Should_set_next_attempt_based_on_num_calls(int calls, int minutes, RuleResult result, RuleJobResult jobResult) public async Task Should_set_next_attempt_based_on_num_calls(int calls, int minutes, RuleResult result, RuleJobResult jobResult)
{ {
var actionData = new RuleJobData(); var actionData = new JObject();
var actionName = "MyAction"; var actionName = "MyAction";
var @event = CreateEvent(calls, actionName, actionData); var @event = CreateEvent(calls, actionName, actionData);
@ -73,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
.MustHaveHappened(); .MustHaveHappened();
} }
private IRuleEventEntity CreateEvent(int numCalls, string actionName, RuleJobData actionData) private IRuleEventEntity CreateEvent(int numCalls, string actionName, JObject actionData)
{ {
var @event = A.Fake<IRuleEventEntity>(); var @event = A.Fake<IRuleEventEntity>();

Loading…
Cancel
Save