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