diff --git a/src/HotChocolate/Data/test/Data.Filters.InMemory.Tests/VariableFilterTests.cs b/src/HotChocolate/Data/test/Data.Filters.InMemory.Tests/VariableFilterTests.cs new file mode 100644 index 00000000000..93dc7597f85 --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Filters.InMemory.Tests/VariableFilterTests.cs @@ -0,0 +1,177 @@ +using HotChocolate.Execution; +using HotChocolate.Types; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Data.Filters; + +public class VariableFilterTests +{ + [Fact] + public async Task Without_Variables() + { + // arrange + var schema = await new ServiceCollection() + .AddGraphQL() + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .AddFiltering() + .AddQueryType() + .AddType() + .AddType() + .AddTypeExtension(typeof(Query)) + .BuildSchemaAsync(); + + // act + var result = await schema.MakeExecutable().ExecuteAsync( + """ + query Q($collectionId: UUID!) { + collection(collectionId: $collectionId) { + id + searchItems( + where: { + latestItem: { + all: { + completeTime: { + neq: "2025-01-01T00:00:00Z" + } + } + } + } + ) { + id + } + } + } + """, + variableValues: new Dictionary + { + { "collectionId", "4d6a4213-1986-4c6c-bb92-18c0b75a5a2e" } + }); + + // assert + Assert.Null(result.ExpectOperationResult().Errors); + } + + [Fact] + public async Task With_Variables() + { + // arrange + var schema = await new ServiceCollection() + .AddGraphQL() + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .AddFiltering() + .AddQueryType() + .AddType() + .AddType() + .AddTypeExtension(typeof(Query)) + .BuildSchemaAsync(); + + // act + var result = await schema.MakeExecutable().ExecuteAsync( + """ + query Q($collectionId: UUID!, $filters: CollectionFilterInput!) { + collection(collectionId: $collectionId) { + id + searchItems(where: $filters) { + id + } + } + } + """, + variableValues: new Dictionary + { + { "collectionId", "4d6a4213-1986-4c6c-bb92-18c0b75a5a2e" }, + { + "filters", + new Dictionary + { + { + "latestItem", + new Dictionary + { + { + "all", + new Dictionary + { + { + "completeTime", + new Dictionary + { + { "neq", "2025-01-01T00:00:00Z" } + } + } + } + } + } + } + } + } + }); + + // assert + Assert.Null(result.ExpectOperationResult().Errors); + } + + [QueryType] + private static class Query + { + [UseSingleOrDefault] + public static IQueryable GetCollection(Guid collectionId) + { + return new List([new Collection { Id = collectionId }]).AsQueryable(); + } + } + + private class Collection + { + public Guid Id { get; set; } + + public IEnumerable? Items { get; set; } = new List(); + } + + private class CollectionType : ObjectType + { + protected override void Configure(IObjectTypeDescriptor descriptor) + { + descriptor.Field("searchItems") + .Type>() + .UseFiltering() + .Resolve( + ctx => ctx.Parent().Items?.AsQueryable() + ?? Enumerable.Empty().AsQueryable()); + } + } + + private class Item + { + public Guid Id { get; set; } + + public DateTime CreateTime { get; set; } + + public DateTime? CompleteTime { get; set; } + } + + private class ItemType : ObjectType; + + private class CollectionFilterInputType : FilterInputType + { + protected override void Configure(IFilterInputTypeDescriptor descriptor) + { + descriptor.BindFieldsExplicitly(); + descriptor + .Field(pa => pa.Items!.OrderByDescending(pao => pao.CreateTime).Take(1)) + .Name("latestItem") + .Type>(); + } + } + + private class ItemFilterInputType : FilterInputType + { + protected override void Configure(IFilterInputTypeDescriptor descriptor) + { + descriptor.BindFieldsExplicitly(); + descriptor + .Field(pao => pao.CompleteTime) + .Name("completeTime"); + } + } +}