mirror of https://github.com/Squidex/squidex.git
Browse Source
* Match regex and query cleanup. * Query by deleted. * Permanent deletion fix and query.pull/671/head
committed by
GitHub
39 changed files with 1005 additions and 543 deletions
@ -0,0 +1,92 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using NodaTime; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Queries; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb |
|||
{ |
|||
internal sealed class AdaptIdVisitor : TransformVisitor<ClrValue, AdaptIdVisitor.Args> |
|||
{ |
|||
private static readonly AdaptIdVisitor Instance = new AdaptIdVisitor(); |
|||
|
|||
public readonly struct Args |
|||
{ |
|||
public readonly DomainId AppId; |
|||
|
|||
public Args(DomainId appId) |
|||
{ |
|||
AppId = appId; |
|||
} |
|||
} |
|||
|
|||
private AdaptIdVisitor() |
|||
{ |
|||
} |
|||
|
|||
public static FilterNode<ClrValue>? AdaptFilter(FilterNode<ClrValue> filter, DomainId appId) |
|||
{ |
|||
var args = new Args(appId); |
|||
|
|||
return filter.Accept(Instance, args); |
|||
} |
|||
|
|||
public override FilterNode<ClrValue> Visit(CompareFilter<ClrValue> nodeIn, Args args) |
|||
{ |
|||
var result = nodeIn; |
|||
|
|||
var (path, _, value) = nodeIn; |
|||
|
|||
var clrValue = value.Value; |
|||
|
|||
if (string.Equals(path[0], "id", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
path = "_id"; |
|||
|
|||
if (clrValue is List<string> idList) |
|||
{ |
|||
value = idList.Select(x => DomainId.Combine(args.AppId, DomainId.Create(x)).ToString()).ToList(); |
|||
} |
|||
else if (clrValue is string id) |
|||
{ |
|||
value = DomainId.Combine(args.AppId, DomainId.Create(id)).ToString(); |
|||
} |
|||
else if (clrValue is List<Guid> guidIdList) |
|||
{ |
|||
value = guidIdList.Select(x => DomainId.Combine(args.AppId, DomainId.Create(x)).ToString()).ToList(); |
|||
} |
|||
else if (clrValue is Guid guidId) |
|||
{ |
|||
value = DomainId.Combine(args.AppId, DomainId.Create(guidId)).ToString(); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (clrValue is List<Guid> guidList) |
|||
{ |
|||
value = guidList.Select(x => x.ToString()).ToList(); |
|||
} |
|||
else if (clrValue is Guid guid) |
|||
{ |
|||
value = guid.ToString(); |
|||
} |
|||
else if (clrValue is Instant && |
|||
!string.Equals(path[0], "mt", StringComparison.OrdinalIgnoreCase) && |
|||
!string.Equals(path[0], "ct", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
value = clrValue.ToString(); |
|||
} |
|||
} |
|||
|
|||
return result with { Path = path, Value = value }; |
|||
} |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.OData.UriParser; |
|||
|
|||
namespace Squidex.Infrastructure.Queries.OData |
|||
{ |
|||
public sealed class ConstantVisitor : QueryNodeVisitor<object> |
|||
{ |
|||
private static readonly ConstantVisitor Instance = new ConstantVisitor(); |
|||
|
|||
private ConstantVisitor() |
|||
{ |
|||
} |
|||
|
|||
public static object Visit(QueryNode node) |
|||
{ |
|||
return node.Accept(Instance); |
|||
} |
|||
|
|||
public override object Visit(ConstantNode nodeIn) |
|||
{ |
|||
return nodeIn.Value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.Queries |
|||
{ |
|||
public static class QueryExtensions |
|||
{ |
|||
public static bool HasFilterField<T>(this Query<T>? query, string field) |
|||
{ |
|||
return HasField(query?.Filter, field); |
|||
} |
|||
|
|||
public static bool HasField<T>(this FilterNode<T>? filter, string field) |
|||
{ |
|||
if (filter == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var fields = new HashSet<string>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
filter.AddFields(fields); |
|||
|
|||
return fields.Contains(field); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Assets.Models |
|||
{ |
|||
public sealed class DeleteAssetDto |
|||
{ |
|||
/// <summary>
|
|||
/// True to check referrers of this asset.
|
|||
/// </summary>
|
|||
[FromQuery] |
|||
public bool CheckReferrers { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// True to delete the asset permanently.
|
|||
/// </summary>
|
|||
[FromQuery] |
|||
public bool Permanent { get; set; } |
|||
|
|||
public DeleteAsset ToCommand(DomainId id) |
|||
{ |
|||
return SimpleMapper.Map(this, new DeleteAsset { AssetId = id }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Domain.Apps.Entities.Contents.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Contents.Models |
|||
{ |
|||
public sealed class DeleteContentDto |
|||
{ |
|||
/// <summary>
|
|||
/// True to check referrers of this content.
|
|||
/// </summary>
|
|||
[FromQuery] |
|||
public bool CheckReferrers { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// True to delete the content permanently.
|
|||
/// </summary>
|
|||
[FromQuery] |
|||
public bool Permanent { get; set; } |
|||
|
|||
public DeleteContent ToCommand(DomainId id) |
|||
{ |
|||
return SimpleMapper.Map(this, new DeleteContent { ContentId = id }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,248 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using FakeItEasy; |
|||
using MongoDB.Bson.Serialization; |
|||
using MongoDB.Driver; |
|||
using NodaTime.Text; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Assets; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.MongoDb.Queries; |
|||
using Squidex.Infrastructure.Queries; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Xunit; |
|||
using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; |
|||
using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.MongoDb |
|||
{ |
|||
public class AssetQueryTests |
|||
{ |
|||
private static readonly IBsonSerializerRegistry Registry = BsonSerializer.SerializerRegistry; |
|||
private static readonly IBsonSerializer<MongoAssetEntity> Serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoAssetEntity>(); |
|||
private readonly DomainId appId = DomainId.NewGuid(); |
|||
|
|||
static AssetQueryTests() |
|||
{ |
|||
DomainIdSerializer.Register(); |
|||
|
|||
TypeConverterStringSerializer<RefToken>.Register(); |
|||
TypeConverterStringSerializer<Status>.Register(); |
|||
|
|||
InstantSerializer.Register(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_for_full_text_search() |
|||
{ |
|||
Assert.Throws<ValidationException>(() => AssertQuery(string.Empty, new ClrQuery { FullText = "Full Text" })); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id() |
|||
{ |
|||
var id = Guid.NewGuid(); |
|||
|
|||
var filter = ClrFilter.Eq("id", id); |
|||
|
|||
AssertQuery($"{{ '_id' : '{appId}--{id}' }}", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id_string() |
|||
{ |
|||
var id = DomainId.NewGuid().ToString(); |
|||
|
|||
var filter = ClrFilter.Eq("id", id); |
|||
|
|||
AssertQuery($"{{ '_id' : '{appId}--{id}' }}", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id_list() |
|||
{ |
|||
var id = Guid.NewGuid(); |
|||
|
|||
var filter = ClrFilter.In("id", new List<Guid> { id }); |
|||
|
|||
AssertQuery($"{{ '_id' : {{ '$in' : ['{appId}--{id}'] }} }}", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id_string_list() |
|||
{ |
|||
var id = DomainId.NewGuid().ToString(); |
|||
|
|||
var filter = ClrFilter.In("id", new List<string> { id }); |
|||
|
|||
AssertQuery($"{{ '_id' : {{ '$in' : ['{appId}--{id}'] }} }}", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_lastModified() |
|||
{ |
|||
var time = "1988-01-19T12:00:00Z"; |
|||
|
|||
var filter = ClrFilter.Eq("lastModified", InstantPattern.ExtendedIso.Parse(time).Value); |
|||
|
|||
AssertQuery("{ 'mt' : ISODate('[value]') }", filter, time); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_lastModifiedBy() |
|||
{ |
|||
var filter = ClrFilter.Eq("lastModifiedBy", "subject:me"); |
|||
|
|||
AssertQuery("{ 'mb' : 'subject:me' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_created() |
|||
{ |
|||
var time = "1988-01-19T12:00:00Z"; |
|||
|
|||
var filter = ClrFilter.Eq("created", InstantPattern.ExtendedIso.Parse(time).Value); |
|||
|
|||
AssertQuery("{ 'ct' : ISODate('[value]') }", filter, time); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_createdBy() |
|||
{ |
|||
var filter = ClrFilter.Eq("createdBy", "subject:me"); |
|||
|
|||
AssertQuery("{ 'cb' : 'subject:me' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_version() |
|||
{ |
|||
var filter = ClrFilter.Eq("version", 2L); |
|||
|
|||
AssertQuery("{ 'vs' : NumberLong(2) }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_fileVersion() |
|||
{ |
|||
var filter = ClrFilter.Eq("fileVersion", 2L); |
|||
|
|||
AssertQuery("{ 'fv' : NumberLong(2) }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_tags() |
|||
{ |
|||
var filter = ClrFilter.Eq("tags", "tag1"); |
|||
|
|||
AssertQuery("{ 'td' : 'tag1' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_fileName() |
|||
{ |
|||
var filter = ClrFilter.Eq("fileName", "Logo.png"); |
|||
|
|||
AssertQuery("{ 'fn' : 'Logo.png' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_mimeType() |
|||
{ |
|||
var filter = ClrFilter.Eq("mimeType", "text/json"); |
|||
|
|||
AssertQuery("{ 'mm' : 'text/json' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_fileSize() |
|||
{ |
|||
var filter = ClrFilter.Eq("fileSize", 1024); |
|||
|
|||
AssertQuery("{ 'fs' : NumberLong(1024) }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_pixelHeight() |
|||
{ |
|||
var filter = ClrFilter.Eq("metadata.pixelHeight", 600); |
|||
|
|||
AssertQuery("{ 'md.pixelHeight' : 600 }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_pixelWidth() |
|||
{ |
|||
var filter = ClrFilter.Eq("metadata.pixelWidth", 800); |
|||
|
|||
AssertQuery("{ 'md.pixelWidth' : 800 }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_orderby_with_single_field() |
|||
{ |
|||
var sorting = SortBuilder.Descending("created"); |
|||
|
|||
AssertSorting("{ 'ct' : -1 }", sorting); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_orderby_with_multiple_fields() |
|||
{ |
|||
var sorting1 = SortBuilder.Ascending("created"); |
|||
var sorting2 = SortBuilder.Descending("createdBy"); |
|||
|
|||
AssertSorting("{ 'ct' : 1, 'cb' : -1 }", sorting1, sorting2); |
|||
} |
|||
|
|||
private void AssertQuery(string expected, FilterNode<ClrValue> filter, object? arg = null) |
|||
{ |
|||
AssertQuery(expected, new ClrQuery { Filter = filter }, arg); |
|||
} |
|||
|
|||
private void AssertQuery(string expected, ClrQuery query, object? arg = null) |
|||
{ |
|||
var rendered = |
|||
query.AdjustToModel(appId).BuildFilter<MongoAssetEntity>(false).Filter! |
|||
.Render(Serializer, Registry).ToString(); |
|||
|
|||
var expectation = Cleanup(expected, arg); |
|||
|
|||
Assert.Equal(expectation, rendered); |
|||
} |
|||
|
|||
private void AssertSorting(string expected, params SortNode[] sort) |
|||
{ |
|||
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); |
|||
|
|||
var rendered = string.Empty; |
|||
|
|||
A.CallTo(() => cursor.Sort(A<SortDefinition<MongoAssetEntity>>._)) |
|||
.Invokes((SortDefinition<MongoAssetEntity> sortDefinition) => |
|||
{ |
|||
rendered = sortDefinition.Render(Serializer, Registry).ToString(); |
|||
}); |
|||
|
|||
cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }.AdjustToModel(appId)); |
|||
|
|||
var expectation = Cleanup(expected); |
|||
|
|||
Assert.Equal(expectation, rendered); |
|||
} |
|||
|
|||
private static string Cleanup(string filter, object? arg = null) |
|||
{ |
|||
return filter.Replace('\'', '"').Replace("[value]", arg?.ToString()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,239 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using FakeItEasy; |
|||
using MongoDB.Bson.Serialization; |
|||
using MongoDB.Driver; |
|||
using NodaTime.Text; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Assets; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.MongoDb.Queries; |
|||
using Squidex.Infrastructure.Queries; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Xunit; |
|||
using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; |
|||
using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
#pragma warning disable IDE1006 // Naming Styles
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.MongoDb |
|||
{ |
|||
public class MongoDbQueryTests |
|||
{ |
|||
private static readonly IBsonSerializerRegistry Registry = BsonSerializer.SerializerRegistry; |
|||
private static readonly IBsonSerializer<MongoAssetEntity> Serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoAssetEntity>(); |
|||
|
|||
static MongoDbQueryTests() |
|||
{ |
|||
DomainIdSerializer.Register(); |
|||
|
|||
TypeConverterStringSerializer<RefToken>.Register(); |
|||
TypeConverterStringSerializer<Status>.Register(); |
|||
|
|||
InstantSerializer.Register(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_for_full_text_search() |
|||
{ |
|||
Assert.Throws<ValidationException>(() => _Q(new ClrQuery { FullText = "Full Text" })); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_lastModified() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); |
|||
var o = _C("{ 'mt' : ISODate('1988-01-19T12:00:00Z') }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_lastModifiedBy() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("lastModifiedBy", "subject:me")); |
|||
var o = _C("{ 'mb' : 'subject:me' }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_created() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); |
|||
var o = _C("{ 'ct' : ISODate('1988-01-19T12:00:00Z') }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_createdBy() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("createdBy", "subject:me")); |
|||
var o = _C("{ 'cb' : 'subject:me' }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_version() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("version", 0)); |
|||
var o = _C("{ 'vs' : NumberLong(0) }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_fileVersion() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("fileVersion", 2)); |
|||
var o = _C("{ 'fv' : NumberLong(2) }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_tags() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("tags", "tag1")); |
|||
var o = _C("{ 'td' : 'tag1' }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_fileName() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("fileName", "Logo.png")); |
|||
var o = _C("{ 'fn' : 'Logo.png' }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_mimeType() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("mimeType", "text/json")); |
|||
var o = _C("{ 'mm' : 'text/json' }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_fileSize() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("fileSize", 1024)); |
|||
var o = _C("{ 'fs' : NumberLong(1024) }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_pixelHeight() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("metadata.pixelHeight", 600)); |
|||
var o = _C("{ 'md.pixelHeight' : 600 }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_pixelWidth() |
|||
{ |
|||
var i = _F(ClrFilter.Eq("metadata.pixelWidth", 800)); |
|||
var o = _C("{ 'md.pixelWidth' : 800 }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_orderby_with_single_field() |
|||
{ |
|||
var i = _S(SortBuilder.Descending("lastModified")); |
|||
var o = _C("{ 'mt' : -1 }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_orderby_with_multiple_fields() |
|||
{ |
|||
var i = _S(SortBuilder.Ascending("lastModified"), SortBuilder.Descending("lastModifiedBy")); |
|||
var o = _C("{ 'mt' : 1, 'mb' : -1 }"); |
|||
|
|||
Assert.Equal(o, i); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_take_statement() |
|||
{ |
|||
var query = new ClrQuery { Take = 3 }; |
|||
|
|||
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); |
|||
|
|||
cursor.QueryLimit(query.AdjustToModel()); |
|||
|
|||
A.CallTo(() => cursor.Limit(3)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_skip_statement() |
|||
{ |
|||
var query = new ClrQuery { Skip = 3 }; |
|||
|
|||
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); |
|||
|
|||
cursor.QuerySkip(query.AdjustToModel()); |
|||
|
|||
A.CallTo(() => cursor.Skip(3)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
private static string _C(string value) |
|||
{ |
|||
return value.Replace('\'', '"'); |
|||
} |
|||
|
|||
private static string _F(FilterNode<ClrValue> filter) |
|||
{ |
|||
return _Q(new ClrQuery { Filter = filter }); |
|||
} |
|||
|
|||
private static string _S(params SortNode[] sorts) |
|||
{ |
|||
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); |
|||
|
|||
var i = string.Empty; |
|||
|
|||
A.CallTo(() => cursor.Sort(A<SortDefinition<MongoAssetEntity>>._)) |
|||
.Invokes((SortDefinition<MongoAssetEntity> sortDefinition) => |
|||
{ |
|||
i = sortDefinition.Render(Serializer, Registry).ToString(); |
|||
}); |
|||
|
|||
cursor.QuerySort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel()); |
|||
|
|||
return i; |
|||
} |
|||
|
|||
private static string _Q(ClrQuery query) |
|||
{ |
|||
var filter = query.AdjustToModel().BuildFilter<MongoAssetEntity>(false).Filter!; |
|||
|
|||
var rendered = filter.Render(Serializer, Registry).ToString(); |
|||
|
|||
return rendered; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,336 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using FakeItEasy; |
|||
using MongoDB.Bson.Serialization; |
|||
using MongoDB.Driver; |
|||
using NodaTime; |
|||
using NodaTime.Text; |
|||
using Squidex.Infrastructure.MongoDb.Queries; |
|||
using Squidex.Infrastructure.Queries; |
|||
using Xunit; |
|||
using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; |
|||
using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; |
|||
|
|||
namespace Squidex.Infrastructure.MongoDb |
|||
{ |
|||
public class MongoQueryTests |
|||
{ |
|||
private readonly IBsonSerializerRegistry registry = BsonSerializer.SerializerRegistry; |
|||
private readonly IBsonSerializer<TestEntity> serializer = BsonSerializer.SerializerRegistry.GetSerializer<TestEntity>(); |
|||
|
|||
public class TestEntity |
|||
{ |
|||
public DomainId Id { get; set; } |
|||
|
|||
public Instant Created { get; set; } |
|||
|
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
public string Text { get; set; } |
|||
|
|||
public long Version { get; set; } |
|||
} |
|||
|
|||
static MongoQueryTests() |
|||
{ |
|||
DomainIdSerializer.Register(); |
|||
|
|||
TypeConverterStringSerializer<RefToken>.Register(); |
|||
|
|||
InstantSerializer.Register(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_throw_exception_for_invalid_field() |
|||
{ |
|||
var filter = ClrFilter.Eq("invalid", "Value"); |
|||
|
|||
AssertQuery("{ 'invalid' : 'Value' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id_guid() |
|||
{ |
|||
var id = Guid.NewGuid(); |
|||
|
|||
var filter = ClrFilter.Eq("Id", id); |
|||
|
|||
AssertQuery("{ '_id' : '[value]' }", filter, id); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id_string() |
|||
{ |
|||
var id = DomainId.NewGuid().ToString(); |
|||
|
|||
var filter = ClrFilter.Eq("Id", id); |
|||
|
|||
AssertQuery("{ '_id' : '[value]' }", filter, id); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id_guid_list() |
|||
{ |
|||
var id = Guid.NewGuid(); |
|||
|
|||
var filter = ClrFilter.In("Id", new List<Guid> { id }); |
|||
|
|||
AssertQuery("{ '_id' : { '$in' : ['[value]'] } }", filter, id); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_id_string_list() |
|||
{ |
|||
var id = DomainId.NewGuid().ToString(); |
|||
|
|||
var filter = ClrFilter.In("Id", new List<string> { id }); |
|||
|
|||
AssertQuery("{ '_id' : { '$in' : ['[value]'] } }", filter, id); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_instant() |
|||
{ |
|||
var time = "1988-01-19T12:00:00Z"; |
|||
|
|||
var filter = ClrFilter.Eq("Version", InstantPattern.ExtendedIso.Parse(time).Value); |
|||
|
|||
AssertQuery("{ 'Version' : ISODate('[value]') }", filter, time); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_reftoken() |
|||
{ |
|||
var filter = ClrFilter.Eq("CreatedBy", "subject:me"); |
|||
|
|||
AssertQuery("{ 'CreatedBy' : 'subject:me' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_reftoken_cleanup() |
|||
{ |
|||
var filter = ClrFilter.Eq("CreatedBy", "me"); |
|||
|
|||
AssertQuery("{ 'CreatedBy' : 'subject:me' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_reftoken_fix() |
|||
{ |
|||
var filter = ClrFilter.Eq("CreatedBy", "user:me"); |
|||
|
|||
AssertQuery("{ 'CreatedBy' : 'subject:me' }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_number() |
|||
{ |
|||
var filter = ClrFilter.Eq("Version", 0L); |
|||
|
|||
AssertQuery("{ 'Version' : NumberLong(0) }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_number_and_list() |
|||
{ |
|||
var filter = ClrFilter.In("Version", new List<long> { 0L, 2L, 5L }); |
|||
|
|||
AssertQuery("{ 'Version' : { '$in' : [NumberLong(0), NumberLong(2), NumberLong(5)] } }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_contains_and_null_value() |
|||
{ |
|||
var filter = ClrFilter.Contains("Text", null!); |
|||
|
|||
AssertQuery("{ 'Text' : /null/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_contains() |
|||
{ |
|||
var filter = ClrFilter.Contains("Text", "search"); |
|||
|
|||
AssertQuery("{ 'Text' : /search/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_endswith_and_null_value() |
|||
{ |
|||
var filter = ClrFilter.EndsWith("Text", null!); |
|||
|
|||
AssertQuery("{ 'Text' : /null$/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_endswith() |
|||
{ |
|||
var filter = ClrFilter.EndsWith("Text", "search"); |
|||
|
|||
AssertQuery("{ 'Text' : /search$/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_startswith_and_null_value() |
|||
{ |
|||
var filter = ClrFilter.StartsWith("Text", null!); |
|||
|
|||
AssertQuery("{ 'Text' : /^null/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_startswith() |
|||
{ |
|||
var filter = ClrFilter.StartsWith("Text", "search"); |
|||
|
|||
AssertQuery("{ 'Text' : /^search/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_matchs_and_null_value() |
|||
{ |
|||
var filter = ClrFilter.Matchs("Text", null!); |
|||
|
|||
AssertQuery("{ 'Text' : /null/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_matchs() |
|||
{ |
|||
var filter = ClrFilter.Matchs("Text", "^search$"); |
|||
|
|||
AssertQuery("{ 'Text' : /^search$/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_matchs_and_regex_syntax() |
|||
{ |
|||
var filter = ClrFilter.Matchs("Text", "/search/i"); |
|||
|
|||
AssertQuery("{ 'Text' : /search/i }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_matchs_and_regex_case_sensitive_syntax() |
|||
{ |
|||
var filter = ClrFilter.Matchs("Text", "/search/"); |
|||
|
|||
AssertQuery("{ 'Text' : /search/ }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_empty_for_class() |
|||
{ |
|||
var filter = ClrFilter.Empty("Text"); |
|||
|
|||
AssertQuery("{ '$or' : [{ 'Text' : { '$exists' : false } }, { 'Text' : null }, { 'Text' : '' }, { 'Text' : { '$size' : 0 } }] }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_exists() |
|||
{ |
|||
var filter = ClrFilter.Exists("Text"); |
|||
|
|||
AssertQuery("{ 'Text' : { '$exists' : true, '$ne' : null } }", filter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_query_with_full_text() |
|||
{ |
|||
var query = new ClrQuery { FullText = "Hello my World" }; |
|||
|
|||
AssertQuery(query, "{ '$text' : { '$search' : 'Hello my World' } }"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_orderby_with_single_field() |
|||
{ |
|||
var sorting = SortBuilder.Descending("Number"); |
|||
|
|||
AssertSorting("{ 'Number' : -1 }", sorting); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_orderby_with_multiple_fields() |
|||
{ |
|||
var sorting1 = SortBuilder.Ascending("Number"); |
|||
var sorting2 = SortBuilder.Descending("Text"); |
|||
|
|||
AssertSorting("{ 'Number' : 1, 'Text' : -1 }", sorting1, sorting2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_take_statement() |
|||
{ |
|||
var query = new ClrQuery { Take = 3 }; |
|||
|
|||
var cursor = A.Fake<IFindFluent<TestEntity, TestEntity>>(); |
|||
|
|||
cursor.QueryLimit(query); |
|||
|
|||
A.CallTo(() => cursor.Limit(3)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_skip_statement() |
|||
{ |
|||
var query = new ClrQuery { Skip = 3 }; |
|||
|
|||
var cursor = A.Fake<IFindFluent<TestEntity, TestEntity>>(); |
|||
|
|||
cursor.QuerySkip(query); |
|||
|
|||
A.CallTo(() => cursor.Skip(3)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
private void AssertQuery(string expected, FilterNode<ClrValue> filter, object? arg = null) |
|||
{ |
|||
AssertQuery(new ClrQuery { Filter = filter }, expected, arg); |
|||
} |
|||
|
|||
private void AssertQuery(ClrQuery query, string expected, object? arg = null) |
|||
{ |
|||
var rendered = |
|||
query.BuildFilter<TestEntity>().Filter! |
|||
.Render(serializer, registry).ToString(); |
|||
|
|||
var expectation = Cleanup(expected, arg); |
|||
|
|||
Assert.Equal(expectation, rendered); |
|||
} |
|||
|
|||
private void AssertSorting(string expected, params SortNode[] sort) |
|||
{ |
|||
var cursor = A.Fake<IFindFluent<TestEntity, TestEntity>>(); |
|||
|
|||
var rendered = string.Empty; |
|||
|
|||
A.CallTo(() => cursor.Sort(A<SortDefinition<TestEntity>>._)) |
|||
.Invokes((SortDefinition<TestEntity> sortDefinition) => |
|||
{ |
|||
rendered = sortDefinition.Render(serializer, registry).ToString(); |
|||
}); |
|||
|
|||
cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }); |
|||
|
|||
var expectation = Cleanup(expected); |
|||
|
|||
Assert.Equal(expectation, rendered); |
|||
} |
|||
|
|||
private static string Cleanup(string filter, object? arg = null) |
|||
{ |
|||
return filter.Replace('\'', '"').Replace("[value]", arg?.ToString()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue