Browse Source

Performance improvements

pull/1/head
Sebastian 10 years ago
parent
commit
1c182ae47f
  1. 2
      src/Squidex.Core/Schemas/Field.cs
  2. 2
      src/Squidex.Core/Schemas/StringFieldEditor.cs
  3. 44
      src/Squidex.Infrastructure/Json/ConverterContractResolver.cs
  4. 35
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
  5. 8
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  6. 27
      src/Squidex.Read.MongoDb/Contents/Visitors/EdmModelExtensions.cs
  7. 47
      src/Squidex.Read/Contents/Builders/EdmModelBuilder.cs
  8. 5
      src/Squidex.Read/Squidex.Read.csproj
  9. 5
      src/Squidex/Config/Domain/ReadModule.cs
  10. 20
      src/Squidex/Config/Domain/Serializers.cs
  11. 6
      src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs
  12. 2
      tests/Squidex.Core.Tests/Schemas/DateTimeFieldPropertiesTests.cs
  13. 14
      tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs
  14. 6
      tests/Squidex.Core.Tests/Schemas/JsonFieldTests.cs
  15. 19
      tests/Squidex.Core.Tests/Schemas/SchemaTests.cs
  16. 10
      tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs
  17. 12
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/AggregateHandlerTests.cs
  18. 84
      tests/Squidex.Infrastructure.Tests/Json/ConverterContractResolverTests.cs
  19. 26
      tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs
  20. 21
      tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs
  21. 26
      tests/Squidex.Read.Tests/Apps/CachingAppProviderTests.cs
  22. 34
      tests/Squidex.Read.Tests/MongoDb/Contents/ODataQueryTests.cs
  23. 28
      tests/Squidex.Read.Tests/Schemas/CachingSchemaProviderTests.cs
  24. 2
      tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs
  25. 5
      tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs
  26. 4
      tests/Squidex.Write.Tests/Squidex.Write.Tests.csproj

2
src/Squidex.Core/Schemas/Field.cs

@ -86,7 +86,7 @@ namespace Squidex.Core.Schemas
var rawErrors = new List<string>();
try
{
var typedValue = value.Type == JTokenType.Null ? null : ConvertValue(value);
var typedValue = value.Type == JTokenType.Null ? null : ConvertValue(value);
foreach (var validator in validators.Value)
{

2
src/Squidex.Core/Schemas/StringFieldEditor.cs

@ -10,9 +10,9 @@ namespace Squidex.Core.Schemas
{
public enum StringFieldEditor
{
Dropdown,
Input,
Markdown,
Dropdown,
Radio,
RichText,
TextArea

44
src/Squidex.Infrastructure/Json/ConverterContractResolver.cs

@ -0,0 +1,44 @@
// ==========================================================================
// ConverterContractResolver.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Squidex.Infrastructure.Json
{
public sealed class ConverterContractResolver : CamelCasePropertyNamesContractResolver
{
private readonly JsonConverter[] converters;
public ConverterContractResolver(params JsonConverter[] converters)
{
this.converters = converters;
}
protected override JsonConverter ResolveContractConverter(Type objectType)
{
var result = base.ResolveContractConverter(objectType);
if (result != null)
{
return result;
}
foreach (var converter in converters)
{
if (converter.CanConvert(objectType))
{
return converter;
}
}
return null;
}
}
}

35
src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs

@ -12,12 +12,13 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData.Core;
using MongoDB.Driver;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Contents;
using Squidex.Read.Contents.Builders;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.MongoDb.Contents.Visitors;
using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Services;
namespace Squidex.Read.MongoDb.Contents
@ -27,6 +28,7 @@ namespace Squidex.Read.MongoDb.Contents
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
private readonly ISchemaProvider schemaProvider;
private readonly EdmModelBuilder modelBuilder;
protected static IndexKeysDefinitionBuilder<MongoContentEntity> IndexKeys
{
@ -36,13 +38,14 @@ namespace Squidex.Read.MongoDb.Contents
}
}
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemaProvider)
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemaProvider, EdmModelBuilder modelBuilder)
{
Guard.NotNull(database, nameof(database));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(schemaProvider, nameof(schemaProvider));
this.database = database;
this.modelBuilder = modelBuilder;
this.schemaProvider = schemaProvider;
}
@ -50,14 +53,16 @@ namespace Squidex.Read.MongoDb.Contents
{
List<IContentEntity> result = null;
await ForSchemaAsync(schemaId, async (collection, schema) =>
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
var parser = schema.ParseQuery(languages, odataQuery);
var model = modelBuilder.BuildEdmModel(schemaEntity, languages);
var parser = model.ParseQuery(odataQuery);
cursor = collection.Find(parser, schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schema);
cursor = collection.Find(parser, schemaEntity.Schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schemaEntity.Schema);
}
catch (NotSupportedException)
{
@ -76,7 +81,7 @@ namespace Squidex.Read.MongoDb.Contents
foreach (var entity in entities)
{
entity.ParseData(schema);
entity.ParseData(schemaEntity.Schema);
}
result = entities.OfType<IContentEntity>().ToList();
@ -89,14 +94,16 @@ namespace Squidex.Read.MongoDb.Contents
{
var result = 0L;
await ForSchemaAsync(schemaId, async (collection, schema) =>
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
var parser = schema.ParseQuery(languages, odataQuery);
var model = modelBuilder.BuildEdmModel(schemaEntity, languages);
var parser = model.ParseQuery(odataQuery);
cursor = collection.Find(parser, schema, nonPublished);
cursor = collection.Find(parser, schemaEntity.Schema, nonPublished);
}
catch (NotSupportedException)
{
@ -121,17 +128,17 @@ namespace Squidex.Read.MongoDb.Contents
{
MongoContentEntity result = null;
await ForSchemaAsync(schemaId, async (collection, schema) =>
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
result = await collection.Find(x => x.Id == id).FirstOrDefaultAsync();
result?.ParseData(schema);
result?.ParseData(schemaEntity.Schema);
});
return result;
}
private async Task ForSchemaAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Schema, Task> action)
private async Task ForSchemaAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntityWithSchema, Task> action)
{
var collection = GetCollection(schemaId);
@ -142,7 +149,7 @@ namespace Squidex.Read.MongoDb.Contents
return;
}
await action(collection, schemaEntity.Schema);
await action(collection, schemaEntity);
}
}
}

8
src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -68,24 +68,24 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.SchemaId.Id, (collection, schema) =>
return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.CreateAsync(@event, headers, x =>
{
SimpleMapper.Map(@event, x);
x.SetData(schema, @event.Data);
x.SetData(schemaEntity.Schema, @event.Data);
});
});
}
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.SchemaId.Id, (collection, schema) =>
return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.UpdateAsync(@event, headers, x =>
{
x.SetData(schema, @event.Data);
x.SetData(schemaEntity.Schema, @event.Data);
});
});
}

27
src/Squidex.Read.MongoDb/Contents/Visitors/EdmModelExtensions.cs

@ -0,0 +1,27 @@
// ==========================================================================
// SchemaExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.OData.Core.UriParser;
using Microsoft.OData.Edm;
namespace Squidex.Read.MongoDb.Contents.Visitors
{
public static class EdmModelExtensions
{
public static ODataUriParser ParseQuery(this IEdmModel model, string query)
{
var path = model.EntityContainer.EntitySets().First().Path.Path.Last().Split('.').Last();
var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative));
return parser;
}
}
}

47
src/Squidex.Read.MongoDb/Contents/Visitors/SchemaExtensions.cs → src/Squidex.Read/Contents/Builders/EdmModelBuilder.cs

@ -1,5 +1,5 @@
// ==========================================================================
// SchemaExtensions.cs
// EdmModelBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -8,19 +8,45 @@
using System;
using System.Collections.Generic;
using Microsoft.OData.Core.UriParser;
using System.Linq;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Read.Schemas;
using Squidex.Read.Utils;
namespace Squidex.Read.MongoDb.Contents.Visitors
namespace Squidex.Read.Contents.Builders
{
public static class SchemaExtensions
public sealed class EdmModelBuilder : CachingProvider
{
public static EdmModel BuildEdmModel(this Schema schema, HashSet<Language> languages)
public EdmModelBuilder(IMemoryCache cache)
: base(cache)
{
}
public IEdmModel BuildEdmModel(ISchemaEntityWithSchema schemaEntity, HashSet<Language> languages)
{
Guard.NotNull(languages, nameof(languages));
Guard.NotNull(schemaEntity, nameof(schemaEntity));
var cacheKey = $"{schemaEntity.Id}_{schemaEntity.Version}_{string.Join(",", languages.Select(x => x.Iso2Code).OrderBy(x => x))}";
var result = Cache.GetOrCreate<IEdmModel>(cacheKey, entry =>
{
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(60);
return BuildEdmModel(schemaEntity.Schema, languages);
});
return result;
}
private static EdmModel BuildEdmModel(Schema schema, HashSet<Language> languages)
{
var model = new EdmModel();
var container = new EdmEntityContainer("Squidex", "Container");
var schemaType = schema.BuildEdmType(languages, x =>
@ -41,18 +67,9 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
model.AddElement(schemaType);
model.AddElement(entityType);
container.AddEntitySet($"{schema.Name}_Set", entityType);
container.AddEntitySet("ContentSet", entityType);
return model;
}
public static ODataUriParser ParseQuery(this Schema schema, HashSet<Language> languages, string query)
{
var model = schema.BuildEdmModel(languages);
var parser = new ODataUriParser(model, new Uri($"{schema.Name}_Set?{query}", UriKind.Relative));
return parser;
}
}
}

5
src/Squidex.Read/Squidex.Read.csproj

@ -16,4 +16,9 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.0" />
<PackageReference Include="NodaTime" Version="2.0.0-beta20170123" />
</ItemGroup>
<ItemGroup>
<Reference Include="IdentityServer4">
<HintPath>C:\Users\mail2\.nuget\packages\identityserver4\1.1.1\lib\netstandard1.4\IdentityServer4.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

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

@ -12,6 +12,7 @@ using Squidex.Read.Apps;
using Squidex.Read.Apps.Services;
using Squidex.Read.Apps.Services.Implementations;
using Squidex.Read.Contents;
using Squidex.Read.Contents.Builders;
using Squidex.Read.History;
using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Services;
@ -49,6 +50,10 @@ namespace Squidex.Config.Domain
builder.RegisterType<SchemaHistoryEventsCreator>()
.As<IHistoryEventsCreator>()
.SingleInstance();
builder.RegisterType<EdmModelBuilder>()
.AsSelf()
.SingleInstance();
}
}
}

20
src/Squidex/Config/Domain/Serializers.cs

@ -10,7 +10,6 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using NodaTime;
using NodaTime.Serialization.JsonNet;
using Squidex.Events;
@ -27,16 +26,15 @@ namespace Squidex.Config.Domain
{
settings.SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry);
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.Converters.Add(new InstantConverter());
settings.Converters.Add(new LanguageConverter());
settings.Converters.Add(new NamedGuidIdConverter());
settings.Converters.Add(new NamedLongIdConverter());
settings.Converters.Add(new NamedStringIdConverter());
settings.Converters.Add(new PropertiesBagConverter());
settings.Converters.Add(new RefTokenConverter());
settings.Converters.Add(new StringEnumConverter());
settings.ContractResolver = new ConverterContractResolver(
new InstantConverter(),
new LanguageConverter(),
new NamedGuidIdConverter(),
new NamedLongIdConverter(),
new NamedStringIdConverter(),
new PropertiesBagConverter(),
new RefTokenConverter(),
new StringEnumConverter());
settings.NullValueHandling = NullValueHandling.Ignore;

6
src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs

@ -35,6 +35,7 @@ namespace Squidex.Pipeline
static ApiExceptionFilterAttribute()
{
AddHandler<DomainObjectNotFoundException>(OnDomainObjectNotFoundException);
AddHandler<DomainObjectVersionException>(OnDomainObjectVersionException);
AddHandler<DomainException>(OnDomainException);
AddHandler<ValidationException>(OnValidationException);
}
@ -44,6 +45,11 @@ namespace Squidex.Pipeline
return new NotFoundResult();
}
private static IActionResult OnDomainObjectVersionException(DomainObjectVersionException ex)
{
return new ObjectResult(new ErrorDto { Message = ex.Message }) { StatusCode = 409 };
}
private static IActionResult OnDomainException(DomainException ex)
{
return new BadRequestObjectResult(new ErrorDto { Message = ex.Message });

2
tests/Squidex.Core.Tests/Schemas/DateTimeFieldPropertiesTests.cs

@ -136,7 +136,7 @@ namespace Squidex.Core.Schemas
private static Instant FutureDays(int days)
{
return SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromDays(days));
return Instant.FromDateTimeUtc(DateTime.UtcNow.Date.AddDays(days));
}
}
}

14
tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
@ -89,9 +90,20 @@ namespace Squidex.Core.Schemas
new[] { "My-DateTime is not a valid value" });
}
[Fact]
public async Task Should_add_errors_if_value_is_another_type()
{
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime" });
await sut.ValidateAsync(CreateValue(123), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-DateTime is not a valid value" });
}
private static Instant FutureDays(int days)
{
return SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromDays(days));
return Instant.FromDateTimeUtc(DateTime.UtcNow.Date.AddDays(days));
}
private static JValue CreateValue(object v)

6
tests/Squidex.Core.Tests/Schemas/JsonFieldTests.cs

@ -39,17 +39,17 @@ namespace Squidex.Core.Schemas
{
var sut = new JsonField(1, "my-json", new JsonFieldProperties { Label = "My-Json" });
await sut.ValidateAsync(CreateValue(null), errors);
await sut.ValidateAsync(CreateValue(new JValue(1)), errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_add_errors_if_datetime_is_required()
public async Task Should_add_errors_if_json_is_required()
{
var sut = new JsonField(1, "my-json", new JsonFieldProperties { Label = "My-Json", IsRequired = true });
await sut.ValidateAsync(CreateValue(null), errors);
await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Json is required" });

19
tests/Squidex.Core.Tests/Schemas/SchemaTests.cs

@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Infrastructure;
using Xunit;
@ -255,9 +256,9 @@ namespace Squidex.Core.Schemas
{
var languages = new HashSet<Language>(new[] { Language.DE, Language.EN });
var json = BuildMixedSchema().BuildSchema(languages, (n, s) => s).ToJson();
var jsonSchema = BuildMixedSchema().BuildSchema(languages, (n, s) => new JsonSchema4 { SchemaReference = s });
Assert.NotNull(json);
Assert.NotNull(jsonSchema);
}
[Fact]
@ -276,19 +277,19 @@ namespace Squidex.Core.Schemas
var schema =
Schema.Create("user", new SchemaProperties { Hints = "The User" })
.AddOrUpdateField(new JsonField(0, "my-json",
.AddOrUpdateField(new JsonField(1, "my-json",
new JsonFieldProperties()))
.AddOrUpdateField(new StringField(1, "my-string1",
.AddOrUpdateField(new StringField(2, "my-string1",
new StringFieldProperties { Label = "My String1", IsLocalizable = true, IsRequired = true, AllowedValues = allowedValues }))
.AddOrUpdateField(new StringField(2, "my-string2",
.AddOrUpdateField(new StringField(3, "my-string2",
new StringFieldProperties { Hints = "My String1" }))
.AddOrUpdateField(new NumberField(3, "my-number",
.AddOrUpdateField(new NumberField(4, "my-number",
new NumberFieldProperties { MinValue = 1, MaxValue = 10 }))
.AddOrUpdateField(new BooleanField(4, "my-boolean",
.AddOrUpdateField(new BooleanField(5, "my-boolean",
new BooleanFieldProperties()))
.AddOrUpdateField(new DateTimeField(5, "my-datetime",
.AddOrUpdateField(new DateTimeField(6, "my-datetime",
new DateTimeFieldProperties { Editor = DateTimeFieldEditor.DateTime }))
.AddOrUpdateField(new DateTimeField(6, "my-date",
.AddOrUpdateField(new DateTimeField(7, "my-date",
new DateTimeFieldProperties { Editor = DateTimeFieldEditor.Date }));
return schema;

10
tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs

@ -293,15 +293,15 @@ namespace Squidex.Core.Schemas
var schema =
Schema.Create("my-schema", new SchemaProperties())
.AddOrUpdateField(new JsonField(0, "my-json",
.AddOrUpdateField(new JsonField(1, "my-json",
new JsonFieldProperties()))
.AddOrUpdateField(new StringField(1, "my-string",
.AddOrUpdateField(new StringField(2, "my-string",
new StringFieldProperties { DefaultValue = "EN-String", IsLocalizable = true }))
.AddOrUpdateField(new NumberField(2, "my-number",
.AddOrUpdateField(new NumberField(3, "my-number",
new NumberFieldProperties { DefaultValue = 123 }))
.AddOrUpdateField(new BooleanField(3, "my-boolean",
.AddOrUpdateField(new BooleanField(4, "my-boolean",
new BooleanFieldProperties { DefaultValue = true }))
.AddOrUpdateField(new DateTimeField(4, "my-datetime",
.AddOrUpdateField(new DateTimeField(5, "my-datetime",
new DateTimeFieldProperties { DefaultValue = now }));
var data =

12
tests/Squidex.Infrastructure.Tests/CQRS/Commands/AggregateHandlerTests.cs

@ -82,6 +82,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
Assert.Equal(repository.Object, sut.Repository);
}
[Fact]
public Task Create_async_should_throw_if_not_aggregate_command()
{
return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.CreateAsync<MyDomainObject>(new CommandContext(new Mock<ICommand>().Object), x => TaskHelper.False));
}
[Fact]
public async Task Create_async_should_create_domain_object_and_save()
{
@ -132,6 +138,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
repository.VerifyAll();
}
[Fact]
public Task Update_async_should_throw_if_not_aggregate_command()
{
return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.UpdateAsync<MyDomainObject>(new CommandContext(new Mock<ICommand>().Object), x => TaskHelper.False));
}
[Fact]
public async Task Update_async_should_create_domain_object_and_save()
{

84
tests/Squidex.Infrastructure.Tests/Json/ConverterContractResolverTests.cs

@ -0,0 +1,84 @@
// ==========================================================================
// ConverterContractResolverTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using NodaTime;
using Squidex.Infrastructure.TestHelpers;
using Xunit;
namespace Squidex.Infrastructure.Json
{
public class ConverterContractResolverTests
{
public class MyClass
{
[JsonConverter(typeof(TodayConverter))]
public Instant MyProperty { get; set; }
}
public sealed class TodayConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue("TODAY");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Instant);
}
}
[Fact]
public void Should_respect_property_converter()
{
var value = Instant.FromDateTimeUtc(DateTime.UtcNow.Date);
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new ConverterContractResolver(new InstantConverter())
};
var json = JsonConvert.SerializeObject(new MyClass { MyProperty = value }, serializerSettings);
Assert.Equal(@"{ ""MyProperty"": ""TODAY"" }", json);
}
[Fact]
public void Should_ignore_other_converters()
{
var value = Instant.FromDateTimeUtc(DateTime.UtcNow.Date);
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new ConverterContractResolver(new InstantConverter())
};
serializerSettings.Converters.Add(new TodayConverter());
var result = JsonConvert.SerializeObject(Tuple.Create(value), serializerSettings);
var output = JsonConvert.DeserializeObject<Tuple<Instant>>(result, serializerSettings);
Assert.Equal(value, output.Item1);
}
[Fact]
public void Should_serialize_and_deserialize_instant()
{
var value = Instant.FromDateTimeUtc(DateTime.UtcNow.Date);
value.SerializeAndDeserialize(new ConverterContractResolver(new InstantConverter()));
}
}
}

26
tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs

@ -0,0 +1,26 @@
// ==========================================================================
// InstantConverterTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Infrastructure.TestHelpers;
using Xunit;
namespace Squidex.Infrastructure.Json
{
public sealed class InstantConverterTests
{
[Fact]
public void Should_serialize_and_deserialize()
{
var value = Instant.FromDateTimeUtc(DateTime.UtcNow.Date);
value.SerializeAndDeserialize(new InstantConverter());
}
}
}

21
tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs

@ -8,13 +8,28 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Xunit;
namespace Squidex.Infrastructure.TestHelpers
{
public static class JsonHelper
{
public static void SerializeAndDeserialize<T>(this T value, JsonConverter converter) where T : class
public static void SerializeAndDeserialize<T>(this T value, IContractResolver contractResolver)
{
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = contractResolver,
NullValueHandling = NullValueHandling.Include
};
var result = JsonConvert.SerializeObject(Tuple.Create(value), serializerSettings);
var output = JsonConvert.DeserializeObject<Tuple<T>>(result, serializerSettings);
Assert.Equal(value, output.Item1);
}
public static void SerializeAndDeserialize<T>(this T value, JsonConverter converter)
{
var serializerSettings = new JsonSerializerSettings();
@ -27,14 +42,14 @@ namespace Squidex.Infrastructure.TestHelpers
Assert.Equal(value, output.Item1);
}
public static void DoesNotDeserialize<T>(string value, JsonConverter converter) where T : class
public static void DoesNotDeserialize<T>(string value, JsonConverter converter)
{
var serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(converter);
serializerSettings.NullValueHandling = NullValueHandling.Include;
Assert.Throws<JsonSerializationException>(() => JsonConvert.DeserializeObject<Tuple<T>>($"{{ \"Item1\": \"{value}\" }}"));
Assert.ThrowsAny<JsonException>(() => JsonConvert.DeserializeObject<Tuple<T>>($"{{ \"Item1\": \"{value}\" }}", serializerSettings));
}
}
}

26
tests/Squidex.Read.Tests/Apps/CachingAppProviderTests.cs

@ -14,7 +14,6 @@ using Moq;
using Squidex.Infrastructure;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Apps.Services.Implementations;
using Squidex.Read.MongoDb.Apps;
using Xunit;
// ReSharper disable ConvertToConstant.Local
@ -27,14 +26,23 @@ namespace Squidex.Read.Apps
private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private readonly Mock<IAppRepository> repository = new Mock<IAppRepository>();
private readonly CachingAppProvider sut;
private readonly MongoAppEntity appV1;
private readonly MongoAppEntity appV2;
private readonly IAppEntity appV1;
private readonly IAppEntity appV2;
private readonly NamedId<Guid> appId = new NamedId<Guid>(Guid.NewGuid(), "my-app");
public CachingAppProviderTests()
{
appV1 = new MongoAppEntity { Name = appId.Name, Id = appId.Id };
appV2 = new MongoAppEntity { Name = appId.Name, Id = appId.Id };
var appV1Mock = new Mock<IAppEntity>();
var appV2Mock = new Mock<IAppEntity>();
appV1Mock.Setup(x => x.Id).Returns(appId.Id);
appV1Mock.Setup(x => x.Name).Returns(appId.Name);
appV2Mock.Setup(x => x.Id).Returns(appId.Id);
appV2Mock.Setup(x => x.Name).Returns(appId.Name);
appV1 = appV1Mock.Object;
appV2 = appV2Mock.Object;
sut = new CachingAppProvider(cache, repository.Object);
}
@ -42,7 +50,7 @@ namespace Squidex.Read.Apps
[Fact]
public async Task Should_also_retrieve_app_by_name_if_retrieved_by_id_before()
{
repository.Setup(x => x.FindAppAsync(appId.Id)).Returns(Task.FromResult<IAppEntity>(appV1));
repository.Setup(x => x.FindAppAsync(appId.Id)).Returns(Task.FromResult(appV1));
await ProvideAppById(appV1);
await ProvideAppByName(appV1);
@ -54,7 +62,7 @@ namespace Squidex.Read.Apps
[Fact]
public async Task Should_also_retrieve_app_by_id_if_retrieved_by_name_before()
{
repository.Setup(x => x.FindAppAsync(appId.Name)).Returns(Task.FromResult<IAppEntity>(appV1));
repository.Setup(x => x.FindAppAsync(appId.Name)).Returns(Task.FromResult(appV1));
await ProvideAppByName(appV1);
await ProvideAppById(appV1);
@ -68,7 +76,7 @@ namespace Squidex.Read.Apps
{
var apps = ProviderResults(appV1, appV2);
repository.Setup(x => x.FindAppAsync(appId.Id)).Returns(() => Task.FromResult<IAppEntity>(apps()));
repository.Setup(x => x.FindAppAsync(appId.Id)).Returns(() => Task.FromResult(apps()));
await ProvideAppById(appV1);
@ -84,7 +92,7 @@ namespace Squidex.Read.Apps
{
var apps = ProviderResults(appV1, appV2);
repository.Setup(x => x.FindAppAsync(appId.Name)).Returns(() => Task.FromResult<IAppEntity>(apps()));
repository.Setup(x => x.FindAppAsync(appId.Name)).Returns(() => Task.FromResult(apps()));
await ProvideAppByName(appV1);

34
tests/Squidex.Read.Tests/MongoDb/Contents/ODataQueryTests.cs

@ -9,13 +9,18 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Microsoft.OData.Edm;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using Moq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Contents.Builders;
using Squidex.Read.MongoDb.Contents.Visitors;
using Squidex.Read.Schemas;
using Xunit;
// ReSharper disable SpecifyACultureInStringConversionExplicitly
@ -39,6 +44,7 @@ namespace Squidex.Read.MongoDb.Contents
private readonly IBsonSerializerRegistry registry = BsonSerializer.SerializerRegistry;
private readonly IBsonSerializer<MongoContentEntity> serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>();
private readonly IEdmModel edmModel;
private readonly HashSet<Language> languages = new HashSet<Language>
{
Language.EN,
@ -50,10 +56,22 @@ namespace Squidex.Read.MongoDb.Contents
InstantSerializer.Register();
}
public ODataQueryTests()
{
var builder = new EdmModelBuilder(new MemoryCache(Options.Create(new MemoryCacheOptions())));
var schemaEntity = new Mock<ISchemaEntityWithSchema>();
schemaEntity.Setup(x => x.Id).Returns(Guid.NewGuid());
schemaEntity.Setup(x => x.Version).Returns(3);
schemaEntity.Setup(x => x.Schema).Returns(schema);
edmModel = builder.BuildEdmModel(schemaEntity.Object, languages);
}
[Fact]
public void Should_parse_query()
{
var parser = schema.ParseQuery(languages, "$filter=data/firstName/de eq 'Sebastian'");
var parser = edmModel.ParseQuery("$filter=data/firstName/de eq 'Sebastian'");
Assert.NotNull(parser);
}
@ -223,7 +241,7 @@ namespace Squidex.Read.MongoDb.Contents
[Fact]
public void Should_set_top()
{
var parser = schema.ParseQuery(languages, "$top=3");
var parser = edmModel.ParseQuery("$top=3");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Take(parser);
@ -234,7 +252,7 @@ namespace Squidex.Read.MongoDb.Contents
[Fact]
public void Should_set_max_top_if_larger()
{
var parser = schema.ParseQuery(languages, "$top=300");
var parser = edmModel.ParseQuery("$top=300");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Take(parser);
@ -245,7 +263,7 @@ namespace Squidex.Read.MongoDb.Contents
[Fact]
public void Should_set_default_top()
{
var parser = schema.ParseQuery(languages, "");
var parser = edmModel.ParseQuery("");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Take(parser);
@ -256,7 +274,7 @@ namespace Squidex.Read.MongoDb.Contents
[Fact]
public void Should_set_skip()
{
var parser = schema.ParseQuery(languages, "$skip=3");
var parser = edmModel.ParseQuery("$skip=3");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Skip(parser);
@ -267,7 +285,7 @@ namespace Squidex.Read.MongoDb.Contents
[Fact]
public void Should_not_set_skip()
{
var parser = schema.ParseQuery(languages, "");
var parser = edmModel.ParseQuery("");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Take(parser);
@ -282,7 +300,7 @@ namespace Squidex.Read.MongoDb.Contents
private string S(string value)
{
var parser = schema.ParseQuery(languages, value);
var parser = edmModel.ParseQuery(value);
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
var i = string.Empty;
@ -299,7 +317,7 @@ namespace Squidex.Read.MongoDb.Contents
private string F(string value)
{
var parser = schema.ParseQuery(languages, value);
var parser = edmModel.ParseQuery(value);
var query = FilterBuilder.Build(parser, schema).Render(serializer, registry).ToString();

28
tests/Squidex.Read.Tests/Schemas/CachingSchemaProviderTests.cs

@ -14,7 +14,6 @@ using Moq;
using Squidex.Infrastructure;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Schemas.Services.Implementations;
using Squidex.Read.MongoDb.Schemas;
using Xunit;
// ReSharper disable ConvertToConstant.Local
@ -27,15 +26,26 @@ namespace Squidex.Read.Schemas
private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private readonly Mock<ISchemaRepository> repository = new Mock<ISchemaRepository>();
private readonly CachingSchemaProvider sut;
private readonly MongoSchemaEntity schemaV1;
private readonly MongoSchemaEntity schemaV2;
private readonly ISchemaEntityWithSchema schemaV1;
private readonly ISchemaEntityWithSchema schemaV2;
private readonly NamedId<Guid> schemaId = new NamedId<Guid>(Guid.NewGuid(), "my-schema");
private readonly NamedId<Guid> appId = new NamedId<Guid>(Guid.NewGuid(), "my-app");
public CachingSchemaProviderTests()
{
schemaV1 = new MongoSchemaEntity { Name = schemaId.Name, Id = schemaId.Id, AppId = appId.Id };
schemaV2 = new MongoSchemaEntity { Name = schemaId.Name, Id = schemaId.Id, AppId = appId.Id };
var schemaV1Mock = new Mock<ISchemaEntityWithSchema>();
var schemaV2Mock = new Mock<ISchemaEntityWithSchema>();
schemaV1Mock.Setup(x => x.Id).Returns(schemaId.Id);
schemaV1Mock.Setup(x => x.Name).Returns(schemaId.Name);
schemaV1Mock.Setup(x => x.AppId).Returns(appId.Id);
schemaV2Mock.Setup(x => x.Id).Returns(schemaId.Id);
schemaV2Mock.Setup(x => x.Name).Returns(schemaId.Name);
schemaV2Mock.Setup(x => x.AppId).Returns(appId.Id);
schemaV1 = schemaV1Mock.Object;
schemaV2 = schemaV2Mock.Object;
sut = new CachingSchemaProvider(cache, repository.Object);
}
@ -43,7 +53,7 @@ namespace Squidex.Read.Schemas
[Fact]
public async Task Should_also_retrieve_schema_by_name_if_retrieved_by_id_before()
{
repository.Setup(x => x.FindSchemaAsync(schemaId.Id)).Returns(Task.FromResult<ISchemaEntityWithSchema>(schemaV1));
repository.Setup(x => x.FindSchemaAsync(schemaId.Id)).Returns(Task.FromResult(schemaV1));
await ProvideSchemaById(schemaV1);
await ProvideSchemaByName(schemaV1);
@ -55,7 +65,7 @@ namespace Squidex.Read.Schemas
[Fact]
public async Task Should_also_retrieve_schema_by_id_if_retrieved_by_name_before()
{
repository.Setup(x => x.FindSchemaAsync(appId.Id, schemaId.Name)).Returns(Task.FromResult<ISchemaEntityWithSchema>(schemaV1));
repository.Setup(x => x.FindSchemaAsync(appId.Id, schemaId.Name)).Returns(Task.FromResult(schemaV1));
await ProvideSchemaByName(schemaV1);
await ProvideSchemaById(schemaV1);
@ -69,7 +79,7 @@ namespace Squidex.Read.Schemas
{
var schemas = ProviderResults(schemaV1, schemaV2);
repository.Setup(x => x.FindSchemaAsync(schemaId.Id)).Returns(() => Task.FromResult<ISchemaEntityWithSchema>(schemas()));
repository.Setup(x => x.FindSchemaAsync(schemaId.Id)).Returns(() => Task.FromResult(schemas()));
await ProvideSchemaById(schemaV1);
@ -85,7 +95,7 @@ namespace Squidex.Read.Schemas
{
var schemas = ProviderResults(schemaV1, schemaV2);
repository.Setup(x => x.FindSchemaAsync(appId.Id, schemaId.Name)).Returns(() => Task.FromResult<ISchemaEntityWithSchema>(schemas()));
repository.Setup(x => x.FindSchemaAsync(appId.Id, schemaId.Name)).Returns(() => Task.FromResult(schemas()));
await ProvideSchemaByName(schemaV1);

2
tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs

@ -73,8 +73,6 @@ namespace Squidex.Write.Contents
{
await sut.HandleAsync(context);
});
Assert.Equal(contentId, context.Result<Guid>());
}
[Fact]

5
tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Moq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Services;
using Squidex.Write.Schemas.Commands;
@ -65,8 +66,6 @@ namespace Squidex.Write.Schemas
{
await sut.HandleAsync(context);
});
Assert.Equal(SchemaName, context.Result<string>());
}
[Fact]
@ -134,7 +133,7 @@ namespace Squidex.Write.Schemas
await sut.HandleAsync(context);
});
Assert.Equal(1, context.Result<long>());
Assert.Equal(1, context.Result<EntityCreatedResult<long>>().IdOrValue);
}
[Fact]

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

@ -5,10 +5,6 @@
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
<RootNamespace>Squidex.Write</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Squidex.Core\Squidex.Core.csproj" />
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />

Loading…
Cancel
Save