Browse Source

Scripting improved.

pull/341/head
Sebastian Stehle 7 years ago
parent
commit
33d1ba07fb
  1. 1
      NuGet.Config
  2. 8
      extensions/Squidex.Extensions/Squidex.Extensions.csproj
  3. 2
      src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  4. 24
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  5. 13
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs
  6. 10
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs
  7. 15
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs
  8. 6
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs
  9. 24
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/CustomProperty.cs
  10. 6
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs
  11. 4
      src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs
  12. 120
      src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  13. 25
      src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs
  14. 43
      src/Squidex.Domain.Apps.Core.Operations/Scripting/NullPropagation.cs
  15. 6
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  16. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  17. 6
      src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  18. 2
      src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  19. 10
      src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  20. 19
      src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/AssetChangedRuleTriggerDto.cs
  21. 34
      src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerSchemaDto.cs
  22. 16
      src/Squidex/Squidex.csproj
  23. 219
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  24. 2
      tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj
  25. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj
  26. 2
      tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj
  27. 2
      tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj
  28. 4
      tests/Squidex.Tests/Squidex.Tests.csproj

1
NuGet.Config

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="myget.org" value="https://www.myget.org/F/jint/api/v3/index.json" protocolVersion="3" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>

8
extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -10,13 +10,13 @@
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="5.2.1" />
<PackageReference Include="Algolia.Search" Version="5.3.1" />
<PackageReference Include="CoreTweet" Version="1.0.0.483" />
<PackageReference Include="Elasticsearch.Net" Version="6.4.0" />
<PackageReference Include="Elasticsearch.Net" Version="6.4.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="NodaTime" Version="2.4.2" />
<PackageReference Include="NodaTime" Version="2.4.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />

2
src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -8,7 +8,7 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fody" Version="3.3.3">
<PackageReference Include="Fody" Version="3.3.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

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

@ -12,6 +12,7 @@ using System.Text;
using System.Text.RegularExpressions;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
@ -21,7 +22,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
public class RuleEventFormatter
{
private const string Undefined = "UNDEFINED";
private const string Undefined = "null";
private const string ScriptSuffix = ")";
private const string ScriptPrefix = "Script(";
private static readonly char[] ContentPlaceholderStartOld = "CONTENT_DATA".ToCharArray();
private static readonly char[] ContentPlaceholderStartNew = "{CONTENT_DATA".ToCharArray();
private static readonly Regex ContentDataPlaceholderOld = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled);
@ -29,13 +32,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private readonly List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)>();
private readonly IJsonSerializer jsonSerializer;
private readonly IRuleUrlGenerator urlGenerator;
private readonly IScriptEngine scriptEngine;
public RuleEventFormatter(IJsonSerializer jsonSerializer, IRuleUrlGenerator urlGenerator)
public RuleEventFormatter(IJsonSerializer jsonSerializer, IRuleUrlGenerator urlGenerator, IScriptEngine scriptEngine)
{
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.jsonSerializer = jsonSerializer;
this.scriptEngine = scriptEngine;
this.urlGenerator = urlGenerator;
AddPattern("APP_ID", AppId);
@ -72,6 +78,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return text;
}
if (text.StartsWith(ScriptPrefix, StringComparison.OrdinalIgnoreCase) && text.EndsWith(ScriptSuffix, StringComparison.OrdinalIgnoreCase))
{
var script = text.Substring(ScriptPrefix.Length, text.Length - ScriptPrefix.Length - ScriptSuffix.Length);
var customFunctions = new Dictionary<string, Func<string>>
{
["contentUrl"] = () => ContentUrl(@event)
};
return scriptEngine.Interpolate("event", @event, script, customFunctions);
}
var current = text.AsSpan();
var sb = new StringBuilder();
@ -186,7 +204,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
if (@event is EnrichedContentEvent contentEvent)
{
return contentEvent.Type.ToString().ToLowerInvariant();
return contentEvent.Type.ToString();
}
return Undefined;

13
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
private readonly NamedContentData contentData;
private HashSet<string> fieldsToDelete;
private Dictionary<string, ContentDataProperty> fieldProperties;
private Dictionary<string, PropertyDescriptor> fieldProperties;
private bool isChanged;
public ContentDataObject(Engine engine, NamedContentData contentData)
@ -55,7 +55,9 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
foreach (var kvp in fieldProperties)
{
if (kvp.Value.ContentField.TryUpdate(out var fieldData))
var value = (ContentDataProperty)kvp.Value;
if (value.ContentField.TryUpdate(out var fieldData))
{
contentData[kvp.Key] = fieldData;
}
@ -109,17 +111,14 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
EnsurePropertiesInitialized();
foreach (var property in fieldProperties)
{
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value);
}
return fieldProperties;
}
private void EnsurePropertiesInitialized()
{
if (fieldProperties == null)
{
fieldProperties = new Dictionary<string, ContentDataProperty>(contentData.Count);
fieldProperties = new Dictionary<string, PropertyDescriptor>(contentData.Count);
foreach (var kvp in contentData)
{

10
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs

@ -7,18 +7,17 @@
using Jint.Native;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public sealed class ContentDataProperty : PropertyDescriptor
public sealed class ContentDataProperty : CustomProperty
{
private readonly ContentDataObject contentData;
private ContentFieldObject contentField;
private JsValue value;
public override JsValue Value
protected override JsValue CustomValue
{
get
{
@ -42,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
contentField.Put(kvp.Key, kvp.Value.Value, true);
}
this.value = new JsValue(contentField);
this.value = contentField;
}
}
}
@ -53,14 +52,13 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
}
public ContentDataProperty(ContentDataObject contentData, ContentFieldObject contentField = null)
: base(null, true, true, true)
{
this.contentData = contentData;
this.contentField = contentField;
if (contentField != null)
{
value = new JsValue(contentField);
value = contentField;
}
}
}

15
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
private readonly ContentDataObject contentData;
private readonly ContentFieldData fieldData;
private HashSet<string> valuesToDelete;
private Dictionary<string, ContentFieldProperty> valueProperties;
private Dictionary<string, PropertyDescriptor> valueProperties;
private bool isChanged;
public ContentFieldData FieldData
@ -67,9 +67,11 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
foreach (var kvp in valueProperties)
{
if (kvp.Value.IsChanged)
var value = (ContentFieldProperty)kvp.Value;
if (value.IsChanged)
{
fieldData[kvp.Key] = kvp.Value.ContentValue;
fieldData[kvp.Key] = value.ContentValue;
}
}
}
@ -114,17 +116,14 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
EnsurePropertiesInitialized();
foreach (var property in valueProperties)
{
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value);
}
return valueProperties;
}
private void EnsurePropertiesInitialized()
{
if (valueProperties == null)
{
valueProperties = new Dictionary<string, ContentFieldProperty>(FieldData.Count);
valueProperties = new Dictionary<string, PropertyDescriptor>(FieldData.Count);
foreach (var kvp in FieldData)
{

6
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -6,19 +6,18 @@
// ==========================================================================
using Jint.Native;
using Jint.Runtime.Descriptors;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public sealed class ContentFieldProperty : PropertyDescriptor
public sealed class ContentFieldProperty : CustomProperty
{
private readonly ContentFieldObject contentField;
private IJsonValue contentValue;
private JsValue value;
private bool isChanged;
public override JsValue Value
protected override JsValue CustomValue
{
get
{
@ -49,7 +48,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
}
public ContentFieldProperty(ContentFieldObject contentField, IJsonValue contentValue = null)
: base(null, true, true, true)
{
this.contentField = contentField;
this.contentValue = contentValue;

24
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/CustomProperty.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint.Runtime.Descriptors;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public abstract class CustomProperty : PropertyDescriptor
{
public CustomProperty()
: base(PropertyFlag.CustomJsValue)
{
Enumerable = true;
Writable = true;
Configurable = true;
}
}
}

6
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs

@ -27,11 +27,11 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
case JsonNull n:
return JsValue.Null;
case JsonScalar<string> s:
return new JsValue(s.Value);
return new JsString(s.Value);
case JsonScalar<bool> b:
return new JsValue(b.Value);
return new JsBoolean(b.Value);
case JsonScalar<double> b:
return new JsValue(b.Value);
return new JsNumber(b.Value);
case JsonObject obj:
return FromObject(obj, engine);
case JsonArray arr:

4
src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs

@ -5,6 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting
@ -19,6 +21,6 @@ namespace Squidex.Domain.Apps.Core.Scripting
bool Evaluate(string name, object context, string script);
string Interpolate(string name, object context, string script);
string Interpolate(string name, object context, string script, Dictionary<string, Func<string>> customFormatters = null);
}
}

120
src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -7,17 +7,19 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Globalization;
using System.Security.Claims;
using Jint;
using Jint.Native;
using Jint.Native.Date;
using Jint.Native.Object;
using Jint.Parser;
using Jint.Runtime;
using Jint.Runtime.Interop;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.Scripting
{
@ -25,12 +27,38 @@ namespace Squidex.Domain.Apps.Core.Scripting
{
public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(200);
static JintScriptEngine()
public sealed class Converter : IObjectConverter
{
var typeMappers = (Dictionary<Type, Func<Engine, object, JsValue>>)typeof(Engine).GetField("TypeMappers", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
public Engine Engine { get; set; }
typeMappers.Add(typeof(NamedContentData), (engine, data) => new ContentDataObject(engine, (NamedContentData)data));
typeMappers.Add(typeof(ClaimsPrincipal), (engine, data) => JintUser.Create(engine, (ClaimsPrincipal)data));
public bool TryConvert(object value, out JsValue result)
{
result = null;
if (value is Enum)
{
result = value.ToString();
return true;
}
switch (value)
{
case IUser user:
result = JintUser.Create(Engine, user);
return true;
case ClaimsPrincipal principal:
result = JintUser.Create(Engine, principal);
return true;
case Instant instant:
result = JsValue.FromObject(Engine, instant.ToDateTimeUtc());
return true;
case NamedContentData content:
result = new ContentDataObject(Engine, content);
return true;
}
return false;
}
}
public void Execute(ScriptContext context, string script)
@ -126,7 +154,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
{
engine.Execute(script);
}
catch (ParserException ex)
catch (ArgumentException ex)
{
throw new ValidationException($"Failed to execute script with javascript syntax error: {ex.Message}", new ValidationError(ex.Message));
}
@ -138,7 +166,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
private Engine CreateScriptEngine(ScriptContext context)
{
var engine = new Engine(options => options.TimeoutInterval(Timeout).Strict());
var engine = CreateScriptEngine();
var contextInstance = new ObjectInstance(engine);
@ -164,11 +192,56 @@ namespace Squidex.Domain.Apps.Core.Scripting
engine.SetValue("ctx", contextInstance);
engine.SetValue("context", contextInstance);
engine.SetValue("slugify", new ClrFunctionInstance(engine, Slugify));
return engine;
}
private Engine CreateScriptEngine(IReferenceResolver resolver = null, Dictionary<string, Func<string>> customFormatters = null)
{
var converter = new Converter();
var engine = new Engine(options =>
{
if (resolver != null)
{
options.SetReferencesResolver(resolver);
}
options.TimeoutInterval(Timeout).Strict().AddObjectConverter(converter);
});
if (customFormatters != null)
{
foreach (var kvp in customFormatters)
{
engine.SetValue(kvp.Key, Safe(kvp.Value));
}
}
converter.Engine = engine;
engine.SetValue("slugify", new ClrFunctionInstance(engine, "slugify", Slugify));
engine.SetValue("formatTime", new ClrFunctionInstance(engine, "formatTime", FormatDate));
engine.SetValue("formatDate", new ClrFunctionInstance(engine, "formatDate", FormatDate));
return engine;
}
private static Func<string> Safe(Func<string> func)
{
return new Func<string>(() =>
{
try
{
return func();
}
catch
{
return "null";
}
});
}
private static JsValue Slugify(JsValue thisObject, JsValue[] arguments)
{
try
@ -189,6 +262,21 @@ namespace Squidex.Domain.Apps.Core.Scripting
}
}
private static JsValue FormatDate(JsValue thisObject, JsValue[] arguments)
{
try
{
var dateValue = ((DateInstance)arguments.At(0)).ToDateTime();
var dateFormat = TypeConverter.ToString(arguments.At(1));
return dateValue.ToString(dateFormat, CultureInfo.InvariantCulture);
}
catch
{
return JsValue.Undefined;
}
}
private static void EnableDisallow(Engine engine)
{
engine.SetValue("disallow", new Action<string>(message =>
@ -214,7 +302,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
try
{
var result =
new Engine(options => options.TimeoutInterval(Timeout).Strict())
CreateScriptEngine(NullPropagation.Instance)
.SetValue(name, context)
.Execute(script)
.GetCompletionValue()
@ -228,22 +316,24 @@ namespace Squidex.Domain.Apps.Core.Scripting
}
}
public string Interpolate(string name, object context, string script)
public string Interpolate(string name, object context, string script, Dictionary<string, Func<string>> customFormatters = null)
{
try
{
var result =
new Engine(options => options.TimeoutInterval(Timeout).Strict())
CreateScriptEngine(NullPropagation.Instance, customFormatters)
.SetValue(name, context)
.Execute(script)
.GetCompletionValue()
.ToObject();
return (string)result;
var converted = result.ToString();
return converted == "undefined" ? "null" : converted;
}
catch
catch (Exception ex)
{
return string.Empty;
return ex.Message;
}
}
}

25
src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs

@ -5,11 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Jint;
using Jint.Runtime.Interop;
using Squidex.Infrastructure.Security;
using Squidex.Shared.Identity;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.Scripting
{
@ -17,6 +20,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
{
private static readonly char[] ClaimSeparators = { '/', '.', ':' };
public static ObjectWrapper Create(Engine engine, IUser user)
{
return CreateUser(engine, user.Id, false, user.Email, user.DisplayName(), user.Claims);
}
public static ObjectWrapper Create(Engine engine, ClaimsPrincipal principal)
{
var id = principal.OpenIdSubject();
@ -28,19 +36,20 @@ namespace Squidex.Domain.Apps.Core.Scripting
id = principal.OpenIdClientId();
}
var name = principal.FindFirst(SquidexClaimTypes.DisplayName)?.Value;
return CreateUser(engine, id, isClient, principal.OpenIdEmail(), name, principal.Claims);
}
private static ObjectWrapper CreateUser(Engine engine, string id, bool isClient, string email, string name, IEnumerable<Claim> allClaims)
{
var claims =
principal.Claims.GroupBy(x => x.Type)
allClaims.GroupBy(x => x.Type)
.ToDictionary(
x => x.Key.Split(ClaimSeparators).Last(),
x => x.Select(y => y.Value).ToArray());
return new ObjectWrapper(engine, new
{
Id = id,
IsClient = isClient,
Email = principal.OpenIdEmail(),
Claims = claims,
});
return new ObjectWrapper(engine, new { id, isClient, email, name, claims });
}
}
}

43
src/Squidex.Domain.Apps.Core.Operations/Scripting/NullPropagation.cs

@ -0,0 +1,43 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint;
using Jint.Native;
using Jint.Runtime.Interop;
using Jint.Runtime.References;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class NullPropagation : IReferenceResolver
{
public static readonly NullPropagation Instance = new NullPropagation();
public bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value)
{
value = reference.GetBase();
return true;
}
public bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value)
{
return value.IsNull() || value.IsUndefined();
}
public bool TryGetCallable(Engine engine, object reference, out JsValue value)
{
value = new ClrFunctionInstance(engine, "anonymous", (thisObj, values) => thisObj);
return true;
}
public bool CheckCoercible(JsValue value)
{
return true;
}
}
}

6
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -14,9 +14,9 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.2" />
<PackageReference Include="NJsonSchema" Version="9.13.2" />
<PackageReference Include="Jint" Version="3.0.0-beta-1427" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.3" />
<PackageReference Include="NJsonSchema" Version="9.13.11" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />

2
src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -15,7 +15,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.5.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.3" />
<PackageReference Include="MongoDB.Driver" Version="2.7.2" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />

6
src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -15,12 +15,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="GraphQL" Version="2.4.0" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.2">
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.2" />
<PackageReference Include="NodaTime" Version="2.4.2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.2.0" />
<PackageReference Include="NodaTime" Version="2.4.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />

2
src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj

@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
<PackageReference Include="NodaTime" Version="2.4.2" />
<PackageReference Include="NodaTime" Version="2.4.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.9.0" />

10
src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -11,15 +11,15 @@
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.2" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.2">
<PackageReference Include="Microsoft.OData.Core" Version="7.5.3" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.2.0" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="NodaTime" Version="2.4.2" />
<PackageReference Include="NodaTime" Version="2.4.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0004" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />

19
src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/AssetChangedRuleTriggerDto.cs

@ -14,24 +14,9 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers
public sealed class AssetChangedRuleTriggerDto : RuleTriggerDto
{
/// <summary>
/// Determines whether to handle the event when an asset is created.
/// Javascript condition when to trigger.
/// </summary>
public bool SendCreate { get; set; }
/// <summary>
/// Determines whether to handle the event when an asset is updated.
/// </summary>
public bool SendUpdate { get; set; }
/// <summary>
/// Determines whether to handle the event when an asset is renamed.
/// </summary>
public bool SendRename { get; set; }
/// <summary>
/// Determines whether to handle the event when an asset is deleted.
/// </summary>
public bool SendDelete { get; set; }
public string Condition { get; set; }
public override RuleTrigger ToTrigger()
{

34
src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerSchemaDto.cs

@ -17,38 +17,8 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers
public Guid SchemaId { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is created.
/// Javascript condition when to trigger.
/// </summary>
public bool SendCreate { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is updated.
/// </summary>
public bool SendUpdate { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is deleted.
/// </summary>
public bool SendDelete { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is published.
/// </summary>
public bool SendPublish { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is unpublished.
/// </summary>
public bool SendUnpublish { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is archived.
/// </summary>
public bool SendArchived { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is restored.
/// </summary>
public bool SendRestore { get; set; }
public string Condition { get; set; }
}
}

16
src/Squidex/Squidex.csproj

@ -54,7 +54,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="5.2.1" />
<PackageReference Include="Algolia.Search" Version="5.3.1" />
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="2.0.0" />
<PackageReference Include="Ben.BlockingDetector" Version="0.0.3" />
<PackageReference Include="EventStore.ClientAPI.NetCore" Version="4.1.0.23" />
@ -74,14 +74,14 @@
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.4" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.2" />
<PackageReference Include="Microsoft.Orleans.Client" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.3" />
<PackageReference Include="Microsoft.Orleans.Client" Version="2.2.0" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.2.0" />
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.2.0" />
<PackageReference Include="MongoDB.Driver" Version="2.7.2" />
<PackageReference Include="NJsonSchema" Version="9.13.2" />
<PackageReference Include="NSwag.AspNetCore" Version="12.0.5" />
<PackageReference Include="NJsonSchema" Version="9.13.11" />
<PackageReference Include="NSwag.AspNetCore" Version="12.0.9" />
<PackageReference Include="OpenCover" Version="4.6.519" />
<PackageReference Include="Orleans.Providers.MongoDB" Version="2.0.1" />
<PackageReference Include="OrleansDashboard" Version="2.1.3" />

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

@ -13,6 +13,7 @@ using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Shared.Identity;
@ -38,7 +39,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => user.Claims)
.Returns(new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "me") });
sut = new RuleEventFormatter(TestUtils.DefaultSerializer, urlGenerator);
A.CallTo(() => urlGenerator.GenerateContentUIUrl(appId, schemaId, contentId))
.Returns("content-url");
sut = new RuleEventFormatter(TestUtils.DefaultSerializer, urlGenerator, new JintScriptEngine());
}
[Fact]
@ -69,91 +73,112 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Assert.Contains("MyEventName", result);
}
[Fact]
public void Should_replace_app_information_from_event()
[Theory]
[InlineData("Name $APP_NAME has id $APP_ID")]
[InlineData("Script(`Name ${event.appId.name} has id ${event.appId.id}`)")]
public void Should_replace_app_information_from_event(string script)
{
var @event = new EnrichedContentEvent { AppId = appId };
var result = sut.Format("Name $APP_NAME has id $APP_ID", @event);
var result = sut.Format(script, @event);
Assert.Equal($"Name my-app has id {appId.Id}", result);
}
[Fact]
public void Should_replace_schema_information_from_event()
[Theory]
[InlineData("Name $SCHEMA_NAME has id $SCHEMA_ID")]
[InlineData("Script(`Name ${event.schemaId.name} has id ${event.schemaId.id}`)")]
public void Should_replace_schema_information_from_event(string script)
{
var @event = new EnrichedContentEvent { SchemaId = schemaId };
var result = sut.Format("Name $SCHEMA_NAME has id $SCHEMA_ID", @event);
var result = sut.Format(script, @event);
Assert.Equal($"Name my-schema has id {schemaId.Id}", result);
}
[Fact]
public void Should_replace_timestamp_information_from_event()
[Theory]
[InlineData("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME")]
[InlineData("Script(`Date: ${formatDate(event.timestamp, 'yyyy-MM-dd')}, Full: ${formatDate(event.timestamp, 'yyyy-MM-dd-hh-mm-ss')}`)")]
public void Should_replace_timestamp_information_from_event(string script)
{
var now = DateTime.UtcNow;
var envelope = new EnrichedContentEvent { Timestamp = Instant.FromDateTimeUtc(now) };
var result = sut.Format("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope);
var result = sut.Format(script, envelope);
Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result);
}
[Fact]
public void Should_format_email_and_display_name_from_user()
[Theory]
[InlineData("From $USER_NAME ($USER_EMAIL)")]
[InlineData("Script(`From ${event.user.name} (${event.user.email})`)")]
public void Should_format_email_and_display_name_from_user(string script)
{
var @event = new EnrichedContentEvent { User = user, Actor = new RefToken(RefTokenType.Subject, "123") };
var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event);
var result = sut.Format(script, @event);
Assert.Equal("From me (me@email.com)", result);
}
[Fact]
public void Should_return_undefined_if_user_is_not_found()
[Theory]
[InlineData("From $USER_NAME ($USER_EMAIL)")]
[InlineData("Script(`From ${event.user.name} (${event.user.email})`)")]
public void Should_return_null_if_user_is_not_found(string script)
{
var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Subject, "123") };
var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event);
var result = sut.Format(script, @event);
Assert.Equal("From UNDEFINED (UNDEFINED)", result);
Assert.Equal("From null (null)", result);
}
[Fact]
public void Should_format_email_and_display_name_from_client()
[Theory]
[InlineData("From $USER_NAME ($USER_EMAIL)")]
[InlineData("Script(`From ${event.user.name} (${event.user.email})`)", Skip = "Not Supported")]
public void Should_return_null_if_user_is_not_found_with_scripting(string script)
{
var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Client, "android") };
var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Subject, "123") };
var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event);
var result = sut.Format(script, @event);
Assert.Equal("From client:android (client:android)", result);
Assert.Equal("From null (null)", result);
}
[Fact]
public void Should_replace_content_url_from_event()
[Theory]
[InlineData("Go to $CONTENT_URL")]
[InlineData("Script(`Go to ${contentUrl()}`)")]
public void Should_replace_content_url_from_event(string script)
{
var url = "http://content";
A.CallTo(() => urlGenerator.GenerateContentUIUrl(appId, schemaId, contentId))
.Returns(url);
var @event = new EnrichedContentEvent { AppId = appId, Id = contentId, SchemaId = schemaId };
var result = sut.Format("Go to $CONTENT_URL", @event);
var result = sut.Format(script, @event);
Assert.Equal($"Go to {url}", result);
Assert.Equal("Go to content-url", result);
}
[Fact]
public void Should_format_content_url_when_not_found()
[Theory]
[InlineData("Go to $CONTENT_URL")]
[InlineData("Script(`Go to ${contentUrl()}`)")]
public void Should_format_content_url_when_not_found(string script)
{
Assert.Equal("UNDEFINED", sut.Format("$CONTENT_URL", new EnrichedAssetEvent()));
Assert.Equal("Go to null", sut.Format(script, new EnrichedAssetEvent()));
}
[Fact]
public void Should_return_undefined_when_field_not_found()
[Theory]
[InlineData("$CONTENT_ACTION")]
[InlineData("Script(`${event.type}`)")]
public void Should_format_content_actions_when_found(string script)
{
Assert.Equal("Created", sut.Format(script, new EnrichedContentEvent { Type = EnrichedContentEventType.Created }));
}
[Theory]
[InlineData("$CONTENT_DATA.country.iv")]
[InlineData("Script(`${event.data.country.iv}`)")]
public void Should_return_null_when_field_not_found(string script)
{
var @event = new EnrichedContentEvent
{
@ -164,13 +189,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin"))
};
var result = sut.Format("$CONTENT_DATA.country.iv", @event);
var result = sut.Format(script, @event);
Assert.Equal("UNDEFINED", result);
Assert.Equal("null", result);
}
[Fact]
public void Should_return_undefined_when_partition_not_found()
[Theory]
[InlineData("$CONTENT_DATA.city.de")]
[InlineData("Script(`${event.data.country.iv}`)")]
public void Should_return_null_when_partition_not_found(string script)
{
var @event = new EnrichedContentEvent
{
@ -181,13 +208,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin"))
};
var result = sut.Format("$CONTENT_DATA.city.de", @event);
var result = sut.Format(script, @event);
Assert.Equal("UNDEFINED", result);
Assert.Equal("null", result);
}
[Fact]
public void Should_return_undefined_when_array_item_not_found()
[Theory]
[InlineData("$CONTENT_DATA.city.iv.10")]
[InlineData("Script(`${event.data.country.de[10]}`)")]
public void Should_return_null_when_array_item_not_found(string script)
{
var @event = new EnrichedContentEvent
{
@ -198,13 +227,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JsonValue.Array()))
};
var result = sut.Format("$CONTENT_DATA.city.de.10", @event);
var result = sut.Format(script, @event);
Assert.Equal("UNDEFINED", result);
Assert.Equal("null", result);
}
[Fact]
public void Should_return_undefined_when_property_not_found()
[Theory]
[InlineData("$CONTENT_DATA.city.de.Name")]
[InlineData("Script(`${event.data.city.de.Location}`)")]
public void Should_return_null_when_property_not_found(string script)
{
var @event = new EnrichedContentEvent
{
@ -215,13 +246,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JsonValue.Object().Add("name", "Berlin")))
};
var result = sut.Format("$CONTENT_DATA.city.de.Name", @event);
var result = sut.Format(script, @event);
Assert.Equal("UNDEFINED", result);
Assert.Equal("null", result);
}
[Fact]
public void Should_return_plain_value_when_found()
[Theory]
[InlineData("$CONTENT_DATA.city.iv")]
[InlineData("Script(`${event.data.city.iv}`)")]
public void Should_return_plain_value_when_found(string script)
{
var @event = new EnrichedContentEvent
{
@ -232,13 +265,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin"))
};
var result = sut.Format("$CONTENT_DATA.city.iv", @event);
var result = sut.Format(script, @event);
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_plain_value_when_found_from_update_event()
[Theory]
[InlineData("$CONTENT_DATA.city.iv.0")]
[InlineData("Script(`${event.data.city.iv[0]}`)")]
public void Should_return_plain_value_from_array_when_found(string script)
{
var @event = new EnrichedContentEvent
{
@ -246,16 +281,19 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new NamedContentData()
.AddField("city",
new ContentFieldData()
.AddValue("iv", "Berlin"))
.AddValue("iv", JsonValue.Array(
"Berlin")))
};
var result = sut.Format("$CONTENT_DATA.city.iv", @event);
var result = sut.Format(script, @event);
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_undefined_when_null()
[Theory]
[InlineData("$CONTENT_DATA.city.iv.name")]
[InlineData("Script(`${event.data.city.iv.name}`)")]
public void Should_return_plain_value_from_object_when_found(string script)
{
var @event = new EnrichedContentEvent
{
@ -263,16 +301,18 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new NamedContentData()
.AddField("city",
new ContentFieldData()
.AddValue("iv", JsonValue.Null))
.AddValue("iv", JsonValue.Object().Add("name", "Berlin")))
};
var result = sut.Format("$CONTENT_DATA.city.iv", @event);
var result = sut.Format(script, @event);
Assert.Equal("UNDEFINED", result);
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_string_when_object()
[Theory]
[InlineData("$CONTENT_DATA.city.iv")]
[InlineData("Script(`${event.data.city.iv}`)", Skip = "Not Supported")]
public void Should_return_json_string_when_object(string script)
{
var @event = new EnrichedContentEvent
{
@ -283,56 +323,27 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JsonValue.Object().Add("name", "Berlin")))
};
var result = sut.Format("$CONTENT_DATA.city.iv", @event);
var result = sut.Format(script, @event);
Assert.Equal("{\"name\":\"Berlin\"}", result);
}
[Fact]
public void Should_return_plain_value_from_array_when_found()
[Theory]
[InlineData("$CONTENT_ACTION")]
public void Should_null_when_content_action_not_found(string script)
{
var @event = new EnrichedContentEvent
{
Data =
new NamedContentData()
.AddField("city",
new ContentFieldData()
.AddValue("iv", JsonValue.Array(
"Berlin")))
};
var result = sut.Format("$CONTENT_DATA.city.iv.0", @event);
Assert.Equal("Berlin", result);
Assert.Equal("null", sut.Format(script, new EnrichedAssetEvent()));
}
[Fact]
public void Should_return_plain_value_from_object_when_found()
{
var @event = new EnrichedContentEvent
[Theory]
[InlineData("From $USER_NAME ($USER_EMAIL)")]
public void Should_format_email_and_display_name_from_client(string script)
{
Data =
new NamedContentData()
.AddField("city",
new ContentFieldData()
.AddValue("iv", JsonValue.Object().Add("name", "Berlin")))
};
var result = sut.Format("$CONTENT_DATA.city.iv.name", @event);
Assert.Equal("Berlin", result);
}
var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Client, "android") };
[Fact]
public void Should_format_content_actions_when_found()
{
Assert.Equal("created", sut.Format("$CONTENT_ACTION", new EnrichedContentEvent { Type = EnrichedContentEventType.Created }));
}
var result = sut.Format(script, @event);
[Fact]
public void Should_format_content_actions_when_not_found()
{
Assert.Equal("UNDEFINED", sut.Format("$CONTENT_ACTION", new EnrichedAssetEvent()));
Assert.Equal("From client:android (client:android)", result);
}
}
}

2
tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj

@ -11,7 +11,7 @@
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="4.9.1" />
<PackageReference Include="FakeItEasy" Version="4.9.2" />
<PackageReference Include="FluentAssertions" Version="5.5.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />

2
tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

@ -16,7 +16,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Core.Tests\Squidex.Domain.Apps.Core.Tests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="4.9.1" />
<PackageReference Include="FakeItEasy" Version="4.9.2" />
<PackageReference Include="FluentAssertions" Version="5.5.3" />
<PackageReference Include="GraphQL" Version="2.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />

2
tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj

@ -12,7 +12,7 @@
<ProjectReference Include="..\..\src\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="4.9.1" />
<PackageReference Include="FakeItEasy" Version="4.9.2" />
<PackageReference Include="FluentAssertions" Version="5.5.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />

2
tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

@ -12,7 +12,7 @@
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="4.9.1" />
<PackageReference Include="FakeItEasy" Version="4.9.2" />
<PackageReference Include="FluentAssertions" Version="5.5.3" />
<PackageReference Include="Google.Cloud.Storage.V1" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />

4
tests/Squidex.Tests/Squidex.Tests.csproj

@ -11,11 +11,11 @@
<ProjectReference Include="..\..\src\Squidex\Squidex.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="4.9.1" />
<PackageReference Include="FakeItEasy" Version="4.9.2" />
<PackageReference Include="IdentityServer4" Version="2.3.2" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="2.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NJsonSchema" Version="9.13.2" />
<PackageReference Include="NJsonSchema" Version="9.13.11" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.4.1" />

Loading…
Cancel
Save