public async Task Can_filter_equality_on_type_Guid()
    {
        // Arrange
        var resource = new FilterableResource
        {
            SomeGuid = Guid.NewGuid()
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, new FilterableResource());
            await dbContext.SaveChangesAsync();
        });

        string route = $"/filterableResources?filter=equals(someGuid,'{resource.SomeGuid}')";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someGuid").With(value => value.Should().Be(resource.SomeGuid));
    }
    public async Task Can_filter_equality_on_type_DateTimeOffset_in_UTC_time_zone()
    {
        // Arrange
        var resource = new FilterableResource
        {
            SomeDateTimeOffset = 27.January(2003).At(11, 22, 33, 44).AsUtc()
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, new FilterableResource());
            await dbContext.SaveChangesAsync();
        });

        string route = $"/filterableResources?filter=equals(someDateTimeOffset,'{HttpUtility.UrlEncode(resource.SomeDateTimeOffset.ToString("O"))}')";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeOffset").With(value => value.Should().Be(resource.SomeDateTimeOffset));
    }
    public async Task Can_filter_equality_on_type(string propertyName, object propertyValue)
    {
        // Arrange
        var          resource = new FilterableResource();
        PropertyInfo?property = typeof(FilterableResource).GetProperty(propertyName);

        property?.SetValue(resource, propertyValue);

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, new FilterableResource());
            await dbContext.SaveChangesAsync();
        });

        string attributeName = propertyName.Camelize();
        string route         = $"/filterableResources?filter=equals({attributeName},'{propertyValue}')";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey(attributeName).With(value => value.Should().Be(value));
    }
    public async Task Cannot_filter_equality_on_incompatible_value()
    {
        // Arrange
        var resource = new FilterableResource
        {
            SomeInt32 = 1
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, new FilterableResource());
            await dbContext.SaveChangesAsync();
        });

        const string route = "/filterableResources?filter=equals(someInt32,'ABC')";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest);

        responseDocument.Errors.ShouldHaveCount(1);

        ErrorObject error = responseDocument.Errors[0];

        error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
        error.Title.Should().Be("Query creation failed due to incompatible types.");
        error.Detail.Should().Be("Failed to convert 'ABC' of type 'String' to type 'Int32'.");
        error.Source.Should().BeNull();
    }
    public async Task Can_filter_comparison_on_whole_number(int matchingValue, int nonMatchingValue, ComparisonOperator filterOperator, double filterValue)
    {
        // Arrange
        var resource = new FilterableResource
        {
            SomeInt32 = matchingValue
        };

        var otherResource = new FilterableResource
        {
            SomeInt32 = nonMatchingValue
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, otherResource);
            await dbContext.SaveChangesAsync();
        });

        string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someInt32,'{filterValue}')";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someInt32").With(value => value.Should().Be(resource.SomeInt32));
    }
    public async Task Can_filter_on_logical_functions(string filterExpression)
    {
        // Arrange
        var resource1 = new FilterableResource
        {
            SomeString = "ABC",
            SomeInt32  = 11,
            SomeEnum   = DayOfWeek.Tuesday
        };

        var resource2 = new FilterableResource
        {
            SomeString = "XYZ",
            SomeInt32  = 99,
            SomeEnum   = DayOfWeek.Saturday
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource1, resource2);
            await dbContext.SaveChangesAsync();
        });

        string route = $"/filterableResources?filter={filterExpression}";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Id.Should().Be(resource1.StringId);
    }
    public async Task Can_filter_equality_on_special_characters()
    {
        // Arrange
        var resource = new FilterableResource
        {
            SomeString = "This, that & more + some"
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, new FilterableResource());
            await dbContext.SaveChangesAsync();
        });

        string route = $"/filterableResources?filter=equals(someString,'{HttpUtility.UrlEncode(resource.SomeString)}')";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someString").With(value => value.Should().Be(resource.SomeString));
    }
    public async Task Can_filter_in_set(string matchingText, string nonMatchingText, string filterText)
    {
        // Arrange
        var resource = new FilterableResource
        {
            SomeString = matchingText
        };

        var otherResource = new FilterableResource
        {
            SomeString = nonMatchingText
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, otherResource);
            await dbContext.SaveChangesAsync();
        });

        string route = $"/filterableResources?filter=any(someString,{filterText})";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someString").With(value => value.Should().Be(resource.SomeString));
    }
    public async Task Can_filter_comparison_on_DateTime_in_UTC_time_zone(string matchingDateTime, string nonMatchingDateTime, ComparisonOperator filterOperator,
                                                                         string filterDateTime)
    {
        // Arrange
        var resource = new FilterableResource
        {
            SomeDateTimeInUtcZone = DateTime.Parse(matchingDateTime, CultureInfo.InvariantCulture).AsUtc()
        };

        var otherResource = new FilterableResource
        {
            SomeDateTimeInUtcZone = DateTime.Parse(nonMatchingDateTime, CultureInfo.InvariantCulture).AsUtc()
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, otherResource);
            await dbContext.SaveChangesAsync();
        });

        string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateTimeInUtcZone,'{filterDateTime}')";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);

        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInUtcZone")
        .With(value => value.Should().Be(resource.SomeDateTimeInUtcZone));
    }
    public async Task Can_filter_is_null_on_type(string propertyName)
    {
        // Arrange
        var          resource = new FilterableResource();
        PropertyInfo?property = typeof(FilterableResource).GetProperty(propertyName);

        property?.SetValue(resource, null);

        var otherResource = new FilterableResource
        {
            SomeNullableString         = "X",
            SomeNullableBoolean        = true,
            SomeNullableInt32          = 1,
            SomeNullableUnsignedInt64  = 1,
            SomeNullableDecimal        = 1,
            SomeNullableDouble         = 1,
            SomeNullableGuid           = Guid.NewGuid(),
            SomeNullableDateTime       = 1.January(2001).AsUtc(),
            SomeNullableDateTimeOffset = 1.January(2001).AsUtc(),
            SomeNullableTimeSpan       = TimeSpan.FromHours(1),
            SomeNullableEnum           = DayOfWeek.Friday
        };

        await _testContext.RunOnDatabaseAsync(async dbContext =>
        {
            await dbContext.ClearTableAsync <FilterableResource>();
            dbContext.FilterableResources.AddRange(resource, otherResource);
            await dbContext.SaveChangesAsync();
        });

        string attributeName = propertyName.Camelize();
        string route         = $"/filterableResources?filter=equals({attributeName},null)";

        // Act
        (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

        // Assert
        httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

        responseDocument.Data.ManyValue.ShouldHaveCount(1);
        responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey(attributeName).With(value => value.Should().BeNull());
    }