From d2b75c4357f93296f94e9fbb41bc15284678eea5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 22 Apr 2017 21:18:51 +0200 Subject: [PATCH] 1) Tests improved. 2) Asset field. --- src/Squidex.Core/Schemas/AssetsField.cs | 51 ++++++ .../Schemas/AssetsFieldProperties.cs | 28 +++ src/Squidex.Core/Schemas/AssetsValue.cs | 25 +++ src/Squidex.Core/Schemas/FieldRegistry.cs | 6 +- src/Squidex.Core/Schemas/GeolocationField.cs | 2 +- src/Squidex.Core/Schemas/IAssetTester.cs | 18 ++ .../Schemas/Validators/AssetsValidator.cs | 57 +++++++ src/Squidex.Core/Squidex.Core.csproj | 3 +- src/Squidex.Events/Assets/AssetCreated.cs | 1 - src/Squidex.Events/Assets/AssetUpdated.cs | 1 - .../GoogleCloudAssetStore.cs | 5 +- .../ImageSharpAssetThumbnailGenerator.cs | 12 +- src/Squidex.Infrastructure/Language.cs | 4 +- .../Squidex.Infrastructure.csproj | 2 +- .../Assets/MongoAssetRepository.cs | 8 +- src/Squidex.Read/Assets/IAssetEntity.cs | 2 - src/Squidex.Write/Assets/AssetDomainObject.cs | 6 - .../Config/Domain/StoreMongoDbModule.cs | 2 + .../Models/AssetsFieldPropertiesDto.cs | 23 +++ .../Models/Converters/SchemaConverter.cs | 11 ++ .../Api/Schemas/Models/FieldPropertiesDto.cs | 1 + src/Squidex/Squidex.csproj | 8 +- .../ContentEnrichmentTests.cs | 5 +- .../Schemas/AssetsFieldPropertiesTests.cs | 60 +++++++ .../Schemas/AssetsFieldTests.cs | 118 +++++++++++++ .../Schemas/FieldRegistryTests.cs | 3 +- .../Schemas/Json/JsonSerializerTests.cs | 5 +- .../Squidex.Core.Tests/Schemas/SchemaTests.cs | 5 +- .../ImageSharpAssetThumbnailGeneratorTests.cs | 65 +++++++ .../Caching/InvalidatingMemoryCacheTest.cs | 6 +- .../LanguageTests.cs | 13 ++ .../Log/SemanticLogAdapterTests.cs | 161 ++++++++++++++++++ .../Schemas/SchemaCommandHandlerTests.cs | 2 +- .../Schemas/SchemaDomainObjectTests.cs | 3 +- 34 files changed, 680 insertions(+), 42 deletions(-) create mode 100644 src/Squidex.Core/Schemas/AssetsField.cs create mode 100644 src/Squidex.Core/Schemas/AssetsFieldProperties.cs create mode 100644 src/Squidex.Core/Schemas/AssetsValue.cs create mode 100644 src/Squidex.Core/Schemas/IAssetTester.cs create mode 100644 src/Squidex.Core/Schemas/Validators/AssetsValidator.cs create mode 100644 src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs create mode 100644 tests/Squidex.Core.Tests/Schemas/AssetsFieldPropertiesTests.cs create mode 100644 tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs create mode 100644 tests/Squidex.Infrastructure.Tests/Assets/ImageSharpAssetThumbnailGeneratorTests.cs create mode 100644 tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs diff --git a/src/Squidex.Core/Schemas/AssetsField.cs b/src/Squidex.Core/Schemas/AssetsField.cs new file mode 100644 index 000000000..10aaee486 --- /dev/null +++ b/src/Squidex.Core/Schemas/AssetsField.cs @@ -0,0 +1,51 @@ +// ========================================================================== +// AssetsField.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Newtonsoft.Json.Linq; +using NJsonSchema; +using Squidex.Core.Schemas.Validators; + +namespace Squidex.Core.Schemas +{ + public sealed class AssetsField : Field + { + private readonly IAssetTester assetTester; + + public AssetsField(long id, string name, AssetsFieldProperties properties, IAssetTester assetTester) + : base(id, name, properties) + { + this.assetTester = assetTester; + } + + protected override IEnumerable CreateValidators() + { + yield return new AssetsValidator(assetTester, Properties.IsRequired); + } + + public override object ConvertValue(JToken value) + { + return new AssetsValue(value.ToObject()); + } + + protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver) + { + var itemSchema = schemaResolver("AssetItem", new JsonSchema4 { Type = JsonObjectType.String }); + + jsonProperty.Type = JsonObjectType.Array; + jsonProperty.Item = itemSchema; + } + + protected override IEdmTypeReference CreateEdmType() + { + return null; + } + } +} diff --git a/src/Squidex.Core/Schemas/AssetsFieldProperties.cs b/src/Squidex.Core/Schemas/AssetsFieldProperties.cs new file mode 100644 index 000000000..05704e0bb --- /dev/null +++ b/src/Squidex.Core/Schemas/AssetsFieldProperties.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// AssetsFieldProperties.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Squidex.Infrastructure; + +namespace Squidex.Core.Schemas +{ + [TypeName("AssetsField")] + public sealed class AssetsFieldProperties : FieldProperties + { + public override JToken GetDefaultValue() + { + return new JArray(); + } + + protected override IEnumerable ValidateCore() + { + yield break; + } + } +} diff --git a/src/Squidex.Core/Schemas/AssetsValue.cs b/src/Squidex.Core/Schemas/AssetsValue.cs new file mode 100644 index 000000000..7e0d5e48b --- /dev/null +++ b/src/Squidex.Core/Schemas/AssetsValue.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// AssetsValue.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; + +namespace Squidex.Core.Schemas +{ + public sealed class AssetsValue + { + private readonly List EmptyAssetIds = new List(); + + public IReadOnlyList AssetIds { get; } + + public AssetsValue(IReadOnlyList assetIds) + { + AssetIds = assetIds ?? EmptyAssetIds; + } + } +} diff --git a/src/Squidex.Core/Schemas/FieldRegistry.cs b/src/Squidex.Core/Schemas/FieldRegistry.cs index 6b4d00d60..71808810d 100644 --- a/src/Squidex.Core/Schemas/FieldRegistry.cs +++ b/src/Squidex.Core/Schemas/FieldRegistry.cs @@ -50,9 +50,10 @@ namespace Squidex.Core.Schemas } } - public FieldRegistry(TypeNameRegistry typeNameRegistry) + public FieldRegistry(TypeNameRegistry typeNameRegistry, IAssetTester assetTester) { Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); + Guard.NotNull(assetTester, nameof(assetTester)); this.typeNameRegistry = typeNameRegistry; @@ -71,6 +72,9 @@ namespace Squidex.Core.Schemas Add( (id, name, p) => new JsonField(id, name, (JsonFieldProperties)p)); + Add( + (id, name, p) => new AssetsField(id, name, (AssetsFieldProperties)p, assetTester)); + Add( (id, name, p) => new GeolocationField(id, name, (GeolocationFieldProperties)p)); } diff --git a/src/Squidex.Core/Schemas/GeolocationField.cs b/src/Squidex.Core/Schemas/GeolocationField.cs index 4e79d8df6..482a61ff4 100644 --- a/src/Squidex.Core/Schemas/GeolocationField.cs +++ b/src/Squidex.Core/Schemas/GeolocationField.cs @@ -47,7 +47,7 @@ namespace Squidex.Core.Schemas var lat = (double)geolocation["latitude"]; var lon = (double)geolocation["longitude"]; - Guard.Between(lat, -90, 90, "latitude"); + Guard.Between(lat, -90, 90, "latitude"); Guard.Between(lon, -180, 180, "longitude"); return value; diff --git a/src/Squidex.Core/Schemas/IAssetTester.cs b/src/Squidex.Core/Schemas/IAssetTester.cs new file mode 100644 index 000000000..0b47552c8 --- /dev/null +++ b/src/Squidex.Core/Schemas/IAssetTester.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// IAssetTester.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; + +namespace Squidex.Core.Schemas +{ + public interface IAssetTester + { + Task IsValidAsync(Guid assetId); + } +} diff --git a/src/Squidex.Core/Schemas/Validators/AssetsValidator.cs b/src/Squidex.Core/Schemas/Validators/AssetsValidator.cs new file mode 100644 index 000000000..5a9af1929 --- /dev/null +++ b/src/Squidex.Core/Schemas/Validators/AssetsValidator.cs @@ -0,0 +1,57 @@ +// ========================================================================== +// AssetsValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Squidex.Core.Schemas.Validators +{ + public sealed class AssetsValidator : IValidator + { + private readonly IAssetTester assetTester; + private readonly bool isRequired; + + public AssetsValidator(IAssetTester assetTester, bool isRequired) + { + this.assetTester = assetTester; + this.isRequired = isRequired; + } + + public async Task ValidateAsync(object value, Action addError) + { + var assets = value as AssetsValue; + + if (assets == null || assets.AssetIds.Count == 0) + { + if (isRequired) + { + addError(" is required"); + } + + return; + } + + var assetTasks = assets.AssetIds.Select(CheckAsset).ToArray(); + + await Task.WhenAll(assetTasks); + + foreach (var notFoundId in assetTasks.Where(x => !x.Result.IsFound).Select(x => x.Result.AssetId)) + { + addError($" contains invalid asset '{notFoundId}'"); + } + } + + private async Task<(Guid AssetId, bool IsFound)> CheckAsset(Guid id) + { + var isFound = await assetTester.IsValidAsync(id); + + return (id, isFound); + } + } +} diff --git a/src/Squidex.Core/Squidex.Core.csproj b/src/Squidex.Core/Squidex.Core.csproj index 315417db2..5ebcc34dd 100644 --- a/src/Squidex.Core/Squidex.Core.csproj +++ b/src/Squidex.Core/Squidex.Core.csproj @@ -14,7 +14,8 @@ - + + diff --git a/src/Squidex.Events/Assets/AssetCreated.cs b/src/Squidex.Events/Assets/AssetCreated.cs index 732f730ad..aceb93266 100644 --- a/src/Squidex.Events/Assets/AssetCreated.cs +++ b/src/Squidex.Events/Assets/AssetCreated.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using Squidex.Infrastructure; namespace Squidex.Events.Assets diff --git a/src/Squidex.Events/Assets/AssetUpdated.cs b/src/Squidex.Events/Assets/AssetUpdated.cs index b6126df02..44fbd4e86 100644 --- a/src/Squidex.Events/Assets/AssetUpdated.cs +++ b/src/Squidex.Events/Assets/AssetUpdated.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using Squidex.Infrastructure; namespace Squidex.Events.Assets diff --git a/src/Squidex.Infrastructure.GoogleCloud/GoogleCloudAssetStore.cs b/src/Squidex.Infrastructure.GoogleCloud/GoogleCloudAssetStore.cs index 03536e108..12b73b5b9 100644 --- a/src/Squidex.Infrastructure.GoogleCloud/GoogleCloudAssetStore.cs +++ b/src/Squidex.Infrastructure.GoogleCloud/GoogleCloudAssetStore.cs @@ -56,10 +56,7 @@ namespace Squidex.Infrastructure.GoogleCloud { throw new AssetNotFoundException($"Asset {id}, {version} not found.", ex); } - else - { - throw; - } + throw; } } diff --git a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs index 2c66b0650..95677da6b 100644 --- a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs +++ b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs @@ -30,6 +30,8 @@ namespace Squidex.Infrastructure.Assets.ImageSharp if (width == null && height == null) { source.CopyTo(destination); + + return; } if (!Enum.TryParse(mode, true, out var resizeMode)) @@ -64,22 +66,16 @@ namespace Squidex.Infrastructure.Assets.ImageSharp { return Task.Run(() => { - ImageInfo imageInfo = null; try { var image = Image.Load(source); - if (image.Width > 0 && image.Height > 0) - { - imageInfo = new ImageInfo(image.Width, image.Height); - } + return new ImageInfo(image.Width, image.Height); } catch { - imageInfo = null; + return null; } - - return imageInfo; }); } } diff --git a/src/Squidex.Infrastructure/Language.cs b/src/Squidex.Infrastructure/Language.cs index b369332cc..cfef595bd 100644 --- a/src/Squidex.Infrastructure/Language.cs +++ b/src/Squidex.Infrastructure/Language.cs @@ -16,10 +16,10 @@ namespace Squidex.Infrastructure { public sealed partial class Language { - private static readonly Regex CultureRegex = new Regex("([a-z]{2})(\\-[a-z]{2})?"); + private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$"); private readonly string iso2Code; private readonly string englishName; - private static readonly Dictionary AllLanguagesField = new Dictionary(); + private static readonly Dictionary AllLanguagesField = new Dictionary(StringComparer.OrdinalIgnoreCase); public static readonly Language Invariant = AddLanguage("iv", "Invariant"); diff --git a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 7f3511261..794cbf92a 100644 --- a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -8,7 +8,7 @@ True - + diff --git a/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs b/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs index 964f523b4..0eb212f9d 100644 --- a/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs +++ b/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Core.Schemas; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb; using Squidex.Read.Assets; @@ -19,7 +20,7 @@ using Squidex.Read.Assets.Repositories; namespace Squidex.Read.MongoDb.Assets { - public partial class MongoAssetRepository : MongoRepositoryBase, IAssetRepository, IEventConsumer + public partial class MongoAssetRepository : MongoRepositoryBase, IAssetRepository, IAssetTester, IEventConsumer { public MongoAssetRepository(IMongoDatabase database) : base(database) @@ -36,6 +37,11 @@ namespace Squidex.Read.MongoDb.Assets return collection.Indexes.CreateOneAsync(IndexKeys.Descending(x => x.LastModified).Ascending(x => x.AppId).Ascending(x => x.FileName).Ascending(x => x.MimeType)); } + public async Task IsValidAsync(Guid assetId) + { + return await Collection.Find(x => x.Id == assetId).CountAsync() == 1; + } + public async Task> QueryAsync(Guid appId, HashSet mimeTypes = null, string query = null, int take = 10, int skip = 0) { var filter = CreateFilter(appId, mimeTypes, query); diff --git a/src/Squidex.Read/Assets/IAssetEntity.cs b/src/Squidex.Read/Assets/IAssetEntity.cs index 7de7623cf..f0902c530 100644 --- a/src/Squidex.Read/Assets/IAssetEntity.cs +++ b/src/Squidex.Read/Assets/IAssetEntity.cs @@ -6,8 +6,6 @@ // All rights reserved. // ========================================================================== -using System; - namespace Squidex.Read.Assets { public interface IAssetEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion diff --git a/src/Squidex.Write/Assets/AssetDomainObject.cs b/src/Squidex.Write/Assets/AssetDomainObject.cs index 031f58a69..3b5104155 100644 --- a/src/Squidex.Write/Assets/AssetDomainObject.cs +++ b/src/Squidex.Write/Assets/AssetDomainObject.cs @@ -24,7 +24,6 @@ namespace Squidex.Write.Assets private bool isDeleted; private long fileVersion; private string fileName; - private Guid fileId; public bool IsDeleted { @@ -36,11 +35,6 @@ namespace Squidex.Write.Assets get { return fileName; } } - public Guid FileId - { - get { return fileId; } - } - public AssetDomainObject(Guid id, int version) : base(id, version) { diff --git a/src/Squidex/Config/Domain/StoreMongoDbModule.cs b/src/Squidex/Config/Domain/StoreMongoDbModule.cs index bfa40ee54..def1f996c 100644 --- a/src/Squidex/Config/Domain/StoreMongoDbModule.cs +++ b/src/Squidex/Config/Domain/StoreMongoDbModule.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.Extensions.Configuration; using MongoDB.Driver; +using Squidex.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb; @@ -136,6 +137,7 @@ namespace Squidex.Config.Domain builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() + .As() .As() .As() .AsSelf() diff --git a/src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs new file mode 100644 index 000000000..9b333e7f9 --- /dev/null +++ b/src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// AssetsFieldPropertiesDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using NJsonSchema.Annotations; +using Squidex.Core.Schemas; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Controllers.Api.Schemas.Models +{ + [JsonSchema("Assets")] + public sealed class AssetsFieldPropertiesDto : FieldPropertiesDto + { + public override FieldProperties ToProperties() + { + return SimpleMapper.Map(this, new AssetsFieldProperties()); + } + } +} diff --git a/src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs b/src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs index d399f1447..d57b244f0 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs @@ -42,6 +42,10 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters { typeof(GeolocationFieldProperties), p => Convert((GeolocationFieldProperties)p) + }, + { + typeof(AssetsFieldProperties), + p => Convert((AssetsFieldProperties)p) } }; @@ -94,6 +98,13 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters return result; } + private static FieldPropertiesDto Convert(AssetsFieldProperties source) + { + var result = SimpleMapper.Map(source, new AssetsFieldPropertiesDto()); + + return result; + } + private static FieldPropertiesDto Convert(StringFieldProperties source) { var result = SimpleMapper.Map(source, new StringFieldPropertiesDto()); diff --git a/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs index 1c8009a8a..b3b40f0c5 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs @@ -15,6 +15,7 @@ using Squidex.Core.Schemas; namespace Squidex.Controllers.Api.Schemas.Models { [JsonConverter(typeof(JsonInheritanceConverter), "fieldType")] + [KnownType(typeof(AssetsFieldPropertiesDto))] [KnownType(typeof(BooleanFieldPropertiesDto))] [KnownType(typeof(DateTimeFieldPropertiesDto))] [KnownType(typeof(GeolocationFieldPropertiesDto))] diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index c2a6313b6..d83245f02 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -39,9 +39,9 @@ - + - + @@ -62,9 +62,9 @@ - + - + diff --git a/tests/Squidex.Core.Tests/ContentEnrichmentTests.cs b/tests/Squidex.Core.Tests/ContentEnrichmentTests.cs index 5b172a510..6a495d741 100644 --- a/tests/Squidex.Core.Tests/ContentEnrichmentTests.cs +++ b/tests/Squidex.Core.Tests/ContentEnrichmentTests.cs @@ -7,6 +7,7 @@ // ========================================================================== using System.Collections.Generic; +using Moq; using NodaTime; using NodaTime.Text; using Squidex.Core.Contents; @@ -38,7 +39,9 @@ namespace Squidex.Core .AddOrUpdateField(new DateTimeField(5, "my-datetime", new DateTimeFieldProperties { DefaultValue = now })) .AddOrUpdateField(new GeolocationField(6, "my-geolocation", - new GeolocationFieldProperties())); + new GeolocationFieldProperties())) + .AddOrUpdateField(new AssetsField(7, "my-assets", + new AssetsFieldProperties(), new Mock().Object)); var data = new ContentData() diff --git a/tests/Squidex.Core.Tests/Schemas/AssetsFieldPropertiesTests.cs b/tests/Squidex.Core.Tests/Schemas/AssetsFieldPropertiesTests.cs new file mode 100644 index 000000000..f7de7b4a9 --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/AssetsFieldPropertiesTests.cs @@ -0,0 +1,60 @@ +// ========================================================================== +// AssetFieldPropertiesTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace Squidex.Core.Schemas +{ + public class AssetsFieldPropertiesTests + { + [Fact] + public void Should_set_or_freeze_sut() + { + var sut = new AssetsFieldProperties(); + + foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) + { + var value = + property.PropertyType.GetTypeInfo().IsValueType ? + Activator.CreateInstance(property.PropertyType) : + null; + + property.SetValue(sut, value); + + var result = property.GetValue(sut); + + Assert.Equal(value, result); + } + + sut.Freeze(); + + foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) + { + var value = + property.PropertyType.GetTypeInfo().IsValueType ? + Activator.CreateInstance(property.PropertyType) : + null; + + Assert.Throws(() => + { + try + { + property.SetValue(sut, value); + } + catch (Exception ex) + { + throw ex.InnerException; + } + }); + } + } + } +} diff --git a/tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs b/tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs new file mode 100644 index 000000000..eceba73ff --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs @@ -0,0 +1,118 @@ +// ========================================================================== +// AssetFieldTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Moq; +using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Tasks; +using Xunit; + +namespace Squidex.Core.Schemas +{ + public class AssetsFieldTests + { + private readonly Mock assetTester = new Mock(); + private readonly List errors = new List(); + + [Fact] + public void Should_instantiate_field() + { + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties(), assetTester.Object); + + Assert.Equal("my-asset", sut.Name); + } + + [Fact] + public void Should_clone_object() + { + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties(), assetTester.Object); + + Assert.NotEqual(sut, sut.Enable()); + } + + [Fact] + public async Task Should_not_add_error_if_assets_are_valid() + { + var assetId = Guid.NewGuid(); + + assetTester.Setup(x => x.IsValidAsync(assetId)).Returns(TaskHelper.True); + + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties(), assetTester.Object); + + await sut.ValidateAsync(CreateValue(assetId), errors); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_assets_are_null_and_valid() + { + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties(), assetTester.Object); + + await sut.ValidateAsync(CreateValue(null), errors); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_add_errors_if_assets_are_required_and_null() + { + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties { IsRequired = true }, assetTester.Object); + + await sut.ValidateAsync(CreateValue(null), errors); + + errors.ShouldBeEquivalentTo( + new[] { " is required" }); + } + + [Fact] + public async Task Should_add_errors_if_assets_are_required_and_empty() + { + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties { IsRequired = true }, assetTester.Object); + + await sut.ValidateAsync(CreateValue(), errors); + + errors.ShouldBeEquivalentTo( + new[] { " is required" }); + } + + [Fact] + public async Task Should_add_errors_if_value_is_not_valid() + { + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties(), assetTester.Object); + + await sut.ValidateAsync("invalid", errors); + + errors.ShouldBeEquivalentTo( + new[] { " is not a valid value" }); + } + + [Fact] + public async Task Should_add_errors_if_asset_are_not_valid() + { + var assetId = Guid.NewGuid(); + + assetTester.Setup(x => x.IsValidAsync(assetId)).Returns(TaskHelper.False); + + var sut = new AssetsField(1, "my-asset", new AssetsFieldProperties(), assetTester.Object); + + await sut.ValidateAsync(CreateValue(assetId), errors); + + errors.ShouldBeEquivalentTo( + new[] { $" contains invalid asset '{assetId}'" }); + } + + private static JToken CreateValue(params Guid[] ids) + { + return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids); + } + } +} diff --git a/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs b/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs index 7f5d439e1..f5752f45c 100644 --- a/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using Moq; using Newtonsoft.Json.Linq; using Squidex.Infrastructure; using Xunit; @@ -16,7 +17,7 @@ namespace Squidex.Core.Schemas { public class FieldRegistryTests { - private readonly FieldRegistry sut = new FieldRegistry(new TypeNameRegistry()); + private readonly FieldRegistry sut = new FieldRegistry(new TypeNameRegistry(), new Mock().Object); private sealed class InvalidProperties : FieldProperties { diff --git a/tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs b/tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs index c4ea8cac8..6f9bcaf60 100644 --- a/tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs @@ -7,6 +7,7 @@ // ========================================================================== using FluentAssertions; +using Moq; using Newtonsoft.Json; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; @@ -25,7 +26,7 @@ namespace Squidex.Core.Schemas.Json serializerSettings.TypeNameHandling = TypeNameHandling.Auto; serializerSettings.SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry); - sut = new SchemaJsonSerializer(new FieldRegistry(typeNameRegistry), serializerSettings); + sut = new SchemaJsonSerializer(new FieldRegistry(typeNameRegistry, new Mock().Object), serializerSettings); } [Fact] @@ -45,6 +46,8 @@ namespace Squidex.Core.Schemas.Json new DateTimeFieldProperties())) .AddOrUpdateField(new GeolocationField(6, "my-geolocation", new GeolocationFieldProperties())) + .AddOrUpdateField(new AssetsField(7, "my-asset", + new AssetsFieldProperties(), new Mock().Object)) .Publish(); var deserialized = sut.Deserialize(sut.Serialize(schema)); diff --git a/tests/Squidex.Core.Tests/Schemas/SchemaTests.cs b/tests/Squidex.Core.Tests/Schemas/SchemaTests.cs index 35b8c8242..5bebbdbbd 100644 --- a/tests/Squidex.Core.Tests/Schemas/SchemaTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/SchemaTests.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Moq; using Newtonsoft.Json.Linq; using NJsonSchema; using Squidex.Infrastructure; @@ -332,7 +333,9 @@ namespace Squidex.Core.Schemas .AddOrUpdateField(new DateTimeField(7, "my-date", new DateTimeFieldProperties { Editor = DateTimeFieldEditor.Date })) .AddOrUpdateField(new GeolocationField(8, "my-geolocation", - new GeolocationFieldProperties())); + new GeolocationFieldProperties())) + .AddOrUpdateField(new AssetsField(9, "my-assets", + new AssetsFieldProperties(), new Mock().Object)); return schema; } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/ImageSharpAssetThumbnailGeneratorTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/ImageSharpAssetThumbnailGeneratorTests.cs new file mode 100644 index 000000000..4af9c285c --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Assets/ImageSharpAssetThumbnailGeneratorTests.cs @@ -0,0 +1,65 @@ +// ========================================================================== +// ImageSharpAssetThumbnailGeneratorTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.IO; +using System.Threading.Tasks; +using Squidex.Infrastructure.Assets.ImageSharp; +using Xunit; + +namespace Squidex.Infrastructure.Assets +{ + public class ImageSharpAssetThumbnailGeneratorTests + { + private const string Image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTM0A1t6AAAADElEQVQYV2P4//8/AAX+Av6nNYGEAAAAAElFTkSuQmCC"; + private readonly ImageSharpAssetThumbnailGenerator sut = new ImageSharpAssetThumbnailGenerator(); + + [Fact] + public async Task Should_return_same_image_if_no_size_is_passed_for_thumbnail() + { + var source = new MemoryStream(Convert.FromBase64String(Image)); + var target = new MemoryStream(); + + await sut.CreateThumbnailAsync(source, target, null, null, "resize"); + + Assert.Equal(target.Length, source.Length); + } + + [Fact] + public async Task Should_resize_image_to_target() + { + var source = new MemoryStream(Convert.FromBase64String(Image)); + var target = new MemoryStream(); + + await sut.CreateThumbnailAsync(source, target, 100, 100, "resize"); + + Assert.True(target.Length > source.Length); + } + + [Fact] + public async Task Should_return_image_information_if_image_is_valid() + { + var source = new MemoryStream(Convert.FromBase64String(Image)); + + var imageInfo = await sut.GetImageInfoAsync(source); + + Assert.Equal(1, imageInfo.PixelHeight); + Assert.Equal(1, imageInfo.PixelWidth); + } + + [Fact] + public async Task Should_return_null_if_stream_is_not_an_image() + { + var source = new MemoryStream(Convert.FromBase64String("YXNkc2Fk")); + + var imageInfo = await sut.GetImageInfoAsync(source); + + Assert.Null(imageInfo); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/Caching/InvalidatingMemoryCacheTest.cs b/tests/Squidex.Infrastructure.Tests/Caching/InvalidatingMemoryCacheTest.cs index 1ebc4e2b5..bbe4fd3de 100644 --- a/tests/Squidex.Infrastructure.Tests/Caching/InvalidatingMemoryCacheTest.cs +++ b/tests/Squidex.Infrastructure.Tests/Caching/InvalidatingMemoryCacheTest.cs @@ -63,7 +63,7 @@ namespace Squidex.Infrastructure.Caching { sut.Invalidate(123); - pubsub.Verify(x => x.Publish("CacheInvalidations", It.IsAny(), false), Times.Never()); + pubsub.Verify(x => x.Publish("CacheInvalidations", It.IsAny(), true), Times.Never()); } [Fact] @@ -71,7 +71,7 @@ namespace Squidex.Infrastructure.Caching { sut.Invalidate("a-key"); - pubsub.Verify(x => x.Publish("CacheInvalidations", "a-key", false), Times.Once()); + pubsub.Verify(x => x.Publish("CacheInvalidations", "a-key", true), Times.Once()); } [Fact] @@ -79,7 +79,7 @@ namespace Squidex.Infrastructure.Caching { ((IMemoryCache)sut).Invalidate("a-key"); - pubsub.Verify(x => x.Publish("CacheInvalidations", "a-key", false), Times.Once()); + pubsub.Verify(x => x.Publish("CacheInvalidations", "a-key", true), Times.Once()); } [Fact] diff --git a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs index 9160ed344..6afdddf17 100644 --- a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs +++ b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs @@ -54,6 +54,18 @@ namespace Squidex.Infrastructure Language.DE.SerializeAndDeserialize(new LanguageConverter()); } + [Fact] + public void Should_return_true_for_valid_language() + { + Assert.True(Language.IsValidLanguage("de")); + } + + [Fact] + public void Should_return_false_for_invalid_language() + { + Assert.False(Language.IsValidLanguage("xx")); + } + [Theory] [InlineData("de", "German")] [InlineData("en", "English")] @@ -65,6 +77,7 @@ namespace Squidex.Infrastructure Assert.Equal(key, language.Iso2Code); Assert.Equal(englishName, language.EnglishName); + Assert.Equal(englishName, language.ToString()); } [Theory] diff --git a/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs b/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs new file mode 100644 index 000000000..07134ac41 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs @@ -0,0 +1,161 @@ +// ========================================================================== +// SemanticLogAdapterTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Moq; +using Squidex.Infrastructure.Log.Adapter; +using Xunit; + +namespace Squidex.Infrastructure.Log +{ + public class SemanticLogAdapterTests + { + private readonly List channels = new List(); + private readonly Lazy log; + private readonly Mock channel = new Mock(); + private readonly SemanticLogLoggerProvider sut; + private string output; + + public SemanticLog Log + { + get { return log.Value; } + } + + public SemanticLogAdapterTests() + { + channels.Add(channel.Object); + + channel.Setup(x => x.Log(It.IsAny(), It.IsAny())).Callback( + new Action((level, message) => + { + output = message; + })); + + log = new Lazy(() => new SemanticLog(channels, new List(), () => new JsonLogWriter())); + + sut = new SemanticLogLoggerProvider(log.Value); + } + + [Fact] + public void Should_do_nothing_when_disposing() + { + sut.Dispose(); + } + + [Fact] + public void Should_provide_a_scope() + { + var logger = sut.CreateLogger("test-category"); + + Assert.NotNull(logger.BeginScope(1)); + } + + [Fact] + public void Should_log_always() + { + var logger = sut.CreateLogger("test-category"); + + Assert.True(logger.IsEnabled(LogLevel.Critical)); + Assert.True(logger.IsEnabled((LogLevel)123)); + } + + [Fact] + public void Should_log_message_with_event_id() + { + var eventId = new EventId(1000); + + var logger = sut.CreateLogger("my-category"); + + logger.Log(LogLevel.Debug, eventId, 1, null, (x, e) => "my-message"); + + var expected = + MakeTestCall(w => w + .WriteProperty("logLevel", "Debug") + .WriteProperty("message", "my-message") + .WriteObject("eventId", e => e + .WriteProperty("id", 1000)) + .WriteProperty("category", "my-category")); + + Assert.Equal(expected, output); + } + + [Fact] + public void Should_log_message_with_event_id_and_name() + { + var eventId = new EventId(1000, "my-event"); + + var logger = sut.CreateLogger("my-category"); + + logger.Log(LogLevel.Debug, eventId, 1, null, (x, e) => "my-message"); + + var expected = + MakeTestCall(w => w + .WriteProperty("logLevel", "Debug") + .WriteProperty("message", "my-message") + .WriteObject("eventId", e => e + .WriteProperty("id", 1000) + .WriteProperty("name", "my-event")) + .WriteProperty("category", "my-category")); + + Assert.Equal(expected, output); + } + + [Fact] + public void Should_log_message_with_exception() + { + var exception = new InvalidOperationException(); + + var logger = sut.CreateLogger("my-category"); + + logger.Log(LogLevel.Debug, new EventId(0), 1, exception, (x, e) => "my-message"); + + var expected = + MakeTestCall(w => w + .WriteProperty("logLevel", "Debug") + .WriteProperty("message", "my-message") + .WriteException(exception) + .WriteProperty("category", "my-category")); + + Assert.Equal(expected, output); + } + + [Theory] + [InlineData(LogLevel.None, "Debug")] + [InlineData(LogLevel.Debug, "Debug")] + [InlineData(LogLevel.Error, "Error")] + [InlineData(LogLevel.Trace, "Trace")] + [InlineData(LogLevel.Warning, "Warning")] + [InlineData(LogLevel.Critical, "Fatal")] + [InlineData(LogLevel.Information, "Information")] + public void Should_log_message(LogLevel level, string semanticLogLevel) + { + var logger = sut.CreateLogger("my-category"); + + logger.Log(level, new EventId(0), 1, null, (x, e) => "my-message"); + + var expected = + MakeTestCall(w => w + .WriteProperty("logLevel", semanticLogLevel) + .WriteProperty("message", "my-message") + .WriteProperty("category", "my-category")); + + Assert.Equal(expected, output); + } + + private static string MakeTestCall(Action writer) + { + IObjectWriter sut = new JsonLogWriter(); + + writer(sut); + + return sut.ToString(); + } + } +} diff --git a/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs b/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs index 6f69fa45c..08e8f91af 100644 --- a/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs +++ b/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs @@ -28,7 +28,7 @@ namespace Squidex.Write.Schemas private readonly Mock schemaProvider = new Mock(); private readonly SchemaCommandHandler sut; private readonly SchemaDomainObject schema; - private readonly FieldRegistry registry = new FieldRegistry(new TypeNameRegistry()); + private readonly FieldRegistry registry = new FieldRegistry(new TypeNameRegistry(), new Mock().Object); private readonly string fieldName = "age"; public SchemaCommandHandlerTests() diff --git a/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs b/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs index 69c8e3f9f..705ef5e0d 100644 --- a/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs +++ b/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; +using Moq; using Squidex.Core.Schemas; using Squidex.Events.Schemas; using Squidex.Infrastructure; @@ -30,7 +31,7 @@ namespace Squidex.Write.Schemas { fieldId = new NamedId(1, fieldName); - var fieldRegistry = new FieldRegistry(new TypeNameRegistry()); + var fieldRegistry = new FieldRegistry(new TypeNameRegistry(), new Mock().Object); sut = new SchemaDomainObject(SchemaId, 0, fieldRegistry); }