// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Collections.Immutable; using FakeItEasy; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Microsoft.OData.Edm; using MongoDB.Bson.Serialization; using MongoDB.Driver; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb.OData; using Xunit; namespace Squidex.Domain.Apps.Entities.Contents.OData { public class ODataQueryTests { private static readonly IBsonSerializerRegistry Registry = BsonSerializer.SerializerRegistry; private static readonly IBsonSerializer Serializer = BsonSerializer.SerializerRegistry.GetSerializer(); private readonly Schema schemaDef; private readonly IEdmModel edmModel; private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); static ODataQueryTests() { InstantSerializer.Register(); } public ODataQueryTests() { schemaDef = new Schema("user") .AddString(1, "firstName", Partitioning.Language, new StringFieldProperties { Label = "FirstName", IsRequired = true, AllowedValues = ImmutableList.Create("1", "2") }) .AddString(2, "lastName", Partitioning.Language, new StringFieldProperties { Hints = "Last Name", Editor = StringFieldEditor.Input }) .AddBoolean(3, "isAdmin", Partitioning.Invariant, new BooleanFieldProperties()) .AddNumber(4, "age", Partitioning.Invariant, new NumberFieldProperties { MinValue = 1, MaxValue = 10 }) .AddDateTime(5, "birthday", Partitioning.Invariant, new DateTimeFieldProperties()) .AddAssets(6, "pictures", Partitioning.Invariant, new AssetsFieldProperties()) .AddReferences(7, "friends", Partitioning.Invariant, new ReferencesFieldProperties()) .AddString(8, "dashed-field", Partitioning.Invariant, new StringFieldProperties()) .Update(new SchemaProperties { Hints = "The User" }); var builder = new EdmModelBuilder(new MemoryCache(Options.Create(new MemoryCacheOptions()))); var schema = A.Dummy(); A.CallTo(() => schema.Id).Returns(Guid.NewGuid()); A.CallTo(() => schema.Version).Returns(3); A.CallTo(() => schema.SchemaDef).Returns(schemaDef); var app = A.Dummy(); A.CallTo(() => app.Id).Returns(Guid.NewGuid()); A.CallTo(() => app.Version).Returns(3); A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); edmModel = builder.BuildEdmModel(schema, app); } [Fact] public void Should_parse_query() { var parser = edmModel.ParseQuery("$filter=data/firstName/de eq 'Sebastian'"); Assert.NotNull(parser); } [Fact] public void Should_make_query_with_created() { var i = F("$filter=created eq 1988-01-19T12:00:00Z"); 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("$filter=createdBy eq 'Me'"); var o = C("{ 'cb' : 'Me' }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_lastModified() { var i = F("$filter=lastModified eq 1988-01-19T12:00:00Z"); 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("$filter=lastModifiedBy eq 'Me'"); var o = C("{ 'mb' : 'Me' }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_version() { var i = F("$filter=version eq 0"); var o = C("{ 'vs' : 0 }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_underscore_field() { var i = F("$filter=data/dashed_field/iv eq 'Value'"); var o = C("{ 'do.8.iv' : 'Value' }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_not() { var i = F("$filter=not endswith(data/firstName/de, 'Sebastian')"); var o = C("{ 'do.1.de' : { '$not' : /Sebastian$/i } }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_startswith() { var i = F("$filter=startswith(data/firstName/de, 'Sebastian')"); var o = C("{ 'do.1.de' : /^Sebastian/i }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_endswith() { var i = F("$filter=endswith(data/firstName/de, 'Sebastian')"); var o = C("{ 'do.1.de' : /Sebastian$/i }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_cointains() { var i = F("$filter=contains(data/firstName/de, 'Sebastian')"); var o = C("{ 'do.1.de' : /Sebastian/i }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_equals() { var i = F("$filter=contains(data/firstName/de, 'Sebastian') eq true"); var o = C("{ 'do.1.de' : /Sebastian/i }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_wih_equals_to_false() { var i = F("$filter=contains(data/firstName/de, 'Sebastian') eq false"); var o = C("{ 'do.1.de' : { '$not' : /Sebastian/i } }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_conjunction_and_contains() { var i = F("$filter=contains(data/firstName/de, 'Sebastian') eq false and data/isAdmin/iv eq true"); var o = C("{ 'do.1.de' : { '$not' : /Sebastian/i }, 'do.3.iv' : true }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_string_equals() { var i = F("$filter=data/firstName/de eq 'Sebastian'"); var o = C("{ 'do.1.de' : 'Sebastian' }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_datetime_equals() { var i = F("$filter=data/birthday/iv eq 1988-01-19T12:00:00Z"); var o = C("{ 'do.5.iv' : ISODate('1988-01-19T12:00:00Z') }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_boolean_equals() { var i = F("$filter=data/isAdmin/iv eq true"); var o = C("{ 'do.3.iv' : true }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_notequals() { var i = F("$filter=data/firstName/de ne 'Sebastian'"); var o = C("{ '$or' : [{ 'do.1.de' : { '$exists' : false } }, { 'do.1.de' : { '$ne' : 'Sebastian' } }] }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_lessthan() { var i = F("$filter=data/age/iv lt 1"); var o = C("{ 'do.4.iv' : { '$lt' : 1.0 } }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_lessequals() { var i = F("$filter=data/age/iv le 1"); var o = C("{ 'do.4.iv' : { '$lte' : 1.0 } }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_greaterthan() { var i = F("$filter=data/age/iv gt 1"); var o = C("{ 'do.4.iv' : { '$gt' : 1.0 } }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_greaterequals() { var i = F("$filter=data/age/iv ge 1"); var o = C("{ 'do.4.iv' : { '$gte' : 1.0 } }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_references_equals() { var i = F("$filter=data/pictures/iv eq 'guid'"); var o = C("{ 'do.6.iv' : 'guid' }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_assets_equals() { var i = F("$filter=data/friends/iv eq 'guid'"); var o = C("{ 'do.7.iv' : 'guid' }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_conjunction() { var i = F("$filter=data/age/iv eq 1 and data/age/iv eq 2"); var o = C("{ '$and' : [{ 'do.4.iv' : 1.0 }, { 'do.4.iv' : 2.0 }] }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_disjunction() { var i = F("$filter=data/age/iv eq 1 or data/age/iv eq 2"); var o = C("{ '$or' : [{ 'do.4.iv' : 1.0 }, { 'do.4.iv' : 2.0 }] }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_full_text() { var i = F("$search=Hello my World"); var o = C("{ '$text' : { '$search' : 'Hello my World' } }"); Assert.Equal(o, i); } [Fact] public void Should_make_query_with_full_text_and_multiple_terms() { var i = F("$search=A and B"); var o = C("{ '$text' : { '$search' : 'A and B' } }"); Assert.Equal(o, i); } [Fact] public void Should_make_orderby_with_single_field() { var i = S("$orderby=data/age/iv desc"); var o = C("{ 'do.4.iv' : -1 }"); Assert.Equal(o, i); } [Fact] public void Should_make_orderby_with_multiple_field() { var i = S("$orderby=data/age/iv, data/firstName/en desc"); var o = C("{ 'do.4.iv' : 1, 'do.1.en' : -1 }"); Assert.Equal(o, i); } [Fact] public void Should_make_top_statement() { var parser = edmModel.ParseQuery("$top=3"); var cursor = A.Fake>(); cursor.ContentTake(parser); A.CallTo(() => cursor.Limit(3)).MustHaveHappened(); } [Fact] public void Should_make_top_statement_with_limit() { var parser = edmModel.ParseQuery("$top=300"); var cursor = A.Fake>(); cursor.ContentTake(parser); A.CallTo(() => cursor.Limit(200)).MustHaveHappened(); } [Fact] public void Should_make_top_statement_with_default_value() { var parser = edmModel.ParseQuery(string.Empty); var cursor = A.Fake>(); cursor.ContentTake(parser); A.CallTo(() => cursor.Limit(20)).MustHaveHappened(); } [Fact] public void Should_make_skip_statement() { var parser = edmModel.ParseQuery("$skip=3"); var cursor = A.Fake>(); cursor.ContentSkip(parser); A.CallTo(() => cursor.Skip(3)).MustHaveHappened(); } [Fact] public void Should_make_skip_statement_with_default_value() { var parser = edmModel.ParseQuery(string.Empty); var cursor = A.Fake>(); cursor.ContentSkip(parser); A.CallTo(() => cursor.Skip(A.Ignored)).MustNotHaveHappened(); } private static string C(string value) { return value.Replace('\'', '"'); } private string S(string value) { var parser = edmModel.ParseQuery(value); var cursor = A.Fake>(); var i = string.Empty; A.CallTo(() => cursor.Sort(A>.Ignored)) .Invokes((SortDefinition sortDefinition) => { i = sortDefinition.Render(Serializer, Registry).ToString(); }); cursor.ContentSort(parser, FindExtensions.CreatePropertyCalculator(schemaDef, false)); return i; } private string F(string value) { var parser = edmModel.ParseQuery(value); var query = parser.BuildFilter(FindExtensions.CreatePropertyCalculator(schemaDef, false)) .Filter.Render(Serializer, Registry).ToString(); return query; } } }