From 72122c5cabc815c681e103ebe21ac3c44b023e84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:54:39 +0000 Subject: [PATCH 1/4] Initial plan From 594f24760c34af411981cd07fdfba576667f0439 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:16:02 +0000 Subject: [PATCH 2/4] Fix GraphQL aggregation features disabled when runtime.graphql section is absent - Fix RuntimeConfig.EnableAggregation to use consistent OR logic (returns true when Runtime is null or Runtime.GraphQL is null, matching IsGraphQLEnabled pattern) - Fix GraphQLSchemaCreator.OnConfigChanged to update _isAggregationEnabled on hot-reload - Add enable-aggregation to dab.draft.schema.json (runtime.graphql section) - Add unit tests: EnableAggregation defaults to true when graphql/runtime section absent, can be explicitly disabled; QueryBuilder adds groupBy to MSSQL connection type but not PostgreSQL Agent-Logs-Url: https://github.com/Azure/data-api-builder/sessions/b4c70fb5-8795-4c8a-a904-d54344395c93 Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- schemas/dab.draft.schema.json | 5 + src/Config/ObjectModel/RuntimeConfig.cs | 8 +- src/Core/Services/GraphQLSchemaCreator.cs | 4 +- .../Configuration/RuntimeConfigLoaderTests.cs | 155 ++++++++++++++++++ .../GraphQLBuilder/QueryBuilderTests.cs | 113 +++++++++++++ 5 files changed, 281 insertions(+), 4 deletions(-) diff --git a/schemas/dab.draft.schema.json b/schemas/dab.draft.schema.json index 4561b06952..25dd39bc95 100644 --- a/schemas/dab.draft.schema.json +++ b/schemas/dab.draft.schema.json @@ -244,6 +244,11 @@ "description": "Maximum allowed depth of a GraphQL query.", "default": null }, + "enable-aggregation": { + "$ref": "#/$defs/boolean-or-string", + "description": "Allow enabling/disabling aggregation (groupBy, sum, avg, min, max, count) for supported database types (MSSQL, DWSQL).", + "default": true + }, "multiple-mutations": { "type": "object", "description": "Configuration properties for multiple mutation operations", diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs index d6bdefc1b8..5d18ec75da 100644 --- a/src/Config/ObjectModel/RuntimeConfig.cs +++ b/src/Config/ObjectModel/RuntimeConfig.cs @@ -194,12 +194,14 @@ Runtime.GraphQL is null || public string DefaultDataSourceName { get; set; } /// - /// Retrieves the value of runtime.graphql.aggregation.enabled property if present, default is true. + /// Retrieves the value of runtime.graphql.enable-aggregation property if present, default is true. + /// Returns true when runtime section is absent, when graphql section is absent, + /// or when enable-aggregation is explicitly set to true. /// [JsonIgnore] public bool EnableAggregation => - Runtime is not null && - Runtime.GraphQL is not null && + Runtime is null || + Runtime.GraphQL is null || Runtime.GraphQL.EnableAggregation; [JsonIgnore] diff --git a/src/Core/Services/GraphQLSchemaCreator.cs b/src/Core/Services/GraphQLSchemaCreator.cs index 90e918c833..229b8b0855 100644 --- a/src/Core/Services/GraphQLSchemaCreator.cs +++ b/src/Core/Services/GraphQLSchemaCreator.cs @@ -80,12 +80,14 @@ public GraphQLSchemaCreator( /// /// Executed when a hot-reload event occurs. Pulls the latest /// runtimeconfig object from the provider and updates the flag indicating - /// whether multiple create operations are enabled, and the entities based on the new config. + /// whether multiple create operations are enabled, whether aggregation is enabled, + /// and the entities based on the new config. /// protected void OnConfigChanged(object? sender, HotReloadEventArgs args) { RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); _isMultipleCreateOperationEnabled = runtimeConfig.IsMultipleCreateOperationEnabled(); + _isAggregationEnabled = runtimeConfig.EnableAggregation; _entities = runtimeConfig.Entities; } diff --git a/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs b/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs index d724753f97..024c84c409 100644 --- a/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs +++ b/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs @@ -283,4 +283,159 @@ public async Task ChildConfigLoadFailureHaltsParentConfigLoading() } } } + + /// + /// Tests that EnableAggregation returns true by default when runtime.graphql section is absent. + /// This is a regression test for the bug where EnableAggregation returned false (disabled) + /// when Runtime.GraphQL was null, even though the default value for EnableAggregation is true. + /// + [TestMethod] + public void EnableAggregation_WhenGraphQLSectionAbsent_DefaultsToTrue() + { + // Arrange: a minimal config with no runtime.graphql section + string configJson = @"{ + ""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"", + ""data-source"": { + ""database-type"": ""mssql"", + ""connection-string"": ""Server=tcp:127.0.0.1,1433;"" + }, + ""runtime"": { + ""host"": { + ""authentication"": { ""provider"": ""StaticWebApps"" } + } + }, + ""entities"": {} + }"; + + IFileSystem fs = new MockFileSystem(new Dictionary() + { + { "dab-config.json", new MockFileData(configJson) } + }); + + FileSystemRuntimeConfigLoader loader = new(fs); + + // Act + bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); + + // Assert + Assert.IsTrue(loaded, "Config should load successfully."); + Assert.IsNull(runtimeConfig.Runtime?.GraphQL, "GraphQL section should be null for this config."); + Assert.IsTrue(runtimeConfig.EnableAggregation, + "EnableAggregation should default to true when runtime.graphql section is absent."); + } + + /// + /// Tests that EnableAggregation returns true by default when runtime section is absent. + /// + [TestMethod] + public void EnableAggregation_WhenRuntimeSectionAbsent_DefaultsToTrue() + { + // Arrange: a minimal config with no runtime section at all + string configJson = @"{ + ""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"", + ""data-source"": { + ""database-type"": ""mssql"", + ""connection-string"": ""Server=tcp:127.0.0.1,1433;"" + }, + ""entities"": {} + }"; + + IFileSystem fs = new MockFileSystem(new Dictionary() + { + { "dab-config.json", new MockFileData(configJson) } + }); + + FileSystemRuntimeConfigLoader loader = new(fs); + + // Act + bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); + + // Assert + Assert.IsTrue(loaded, "Config should load successfully."); + Assert.IsNull(runtimeConfig.Runtime, "Runtime section should be null for this config."); + Assert.IsTrue(runtimeConfig.EnableAggregation, + "EnableAggregation should default to true when runtime section is absent."); + } + + /// + /// Tests that EnableAggregation returns false when explicitly disabled in config. + /// + [TestMethod] + public void EnableAggregation_WhenExplicitlyDisabled_ReturnsFalse() + { + // Arrange: a config with enable-aggregation explicitly set to false + string configJson = @"{ + ""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"", + ""data-source"": { + ""database-type"": ""mssql"", + ""connection-string"": ""Server=tcp:127.0.0.1,1433;"" + }, + ""runtime"": { + ""graphql"": { + ""enabled"": true, + ""enable-aggregation"": false + }, + ""host"": { + ""authentication"": { ""provider"": ""StaticWebApps"" } + } + }, + ""entities"": {} + }"; + + IFileSystem fs = new MockFileSystem(new Dictionary() + { + { "dab-config.json", new MockFileData(configJson) } + }); + + FileSystemRuntimeConfigLoader loader = new(fs); + + // Act + bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); + + // Assert + Assert.IsTrue(loaded, "Config should load successfully."); + Assert.IsFalse(runtimeConfig.EnableAggregation, + "EnableAggregation should be false when explicitly set to false in config."); + } + + /// + /// Tests that EnableAggregation returns true when explicitly enabled in config. + /// + [TestMethod] + public void EnableAggregation_WhenExplicitlyEnabled_ReturnsTrue() + { + // Arrange: a config with enable-aggregation explicitly set to true + string configJson = @"{ + ""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"", + ""data-source"": { + ""database-type"": ""mssql"", + ""connection-string"": ""Server=tcp:127.0.0.1,1433;"" + }, + ""runtime"": { + ""graphql"": { + ""enabled"": true, + ""enable-aggregation"": true + }, + ""host"": { + ""authentication"": { ""provider"": ""StaticWebApps"" } + } + }, + ""entities"": {} + }"; + + IFileSystem fs = new MockFileSystem(new Dictionary() + { + { "dab-config.json", new MockFileData(configJson) } + }); + + FileSystemRuntimeConfigLoader loader = new(fs); + + // Act + bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); + + // Assert + Assert.IsTrue(loaded, "Config should load successfully."); + Assert.IsTrue(runtimeConfig.EnableAggregation, + "EnableAggregation should be true when explicitly set to true in config."); + } } diff --git a/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs index c257a86054..9e7feb303f 100644 --- a/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs @@ -538,6 +538,119 @@ public void GenerateReturnType_IncludesGroupByField() Assert.AreEqual("BookGroupBy", groupByType.Name.Value, "should return GroupBy type"); } + /// + /// Tests that the return type does NOT include the groupBy field when aggregation is disabled. + /// + [TestMethod] + [TestCategory("Query Builder - Return Type")] + public void GenerateReturnType_ExcludesGroupByField_WhenAggregationDisabled() + { + // Arrange + NameNode entityName = new("Book"); + + // Act + ObjectTypeDefinitionNode returnType = QueryBuilder.GenerateReturnType(entityName, isAggregationEnabled: false); + + // Assert + FieldDefinitionNode groupByField = returnType.Fields.FirstOrDefault(f => f.Name.Value == "groupBy"); + Assert.IsNull(groupByField, "groupBy field should NOT exist when aggregation is disabled"); + } + + /// + /// Tests that QueryBuilder.Build adds a groupBy field to the connection type + /// for MSSQL entities when aggregation is enabled. This verifies the fix for the + /// regression where aggregation features were not accessible in the schema. + /// + [TestMethod] + [TestCategory("Query Builder - Aggregation")] + public void Build_WithMssqlAndAggregationEnabled_AddsGroupByToConnectionType() + { + // Arrange + string gql = @" +type Book @model(name:""Book"") { + id: ID! + price: Float! + title: String +}"; + + DocumentNode root = Utf8GraphQLParser.Parse(gql); + Dictionary entityNameToDatabaseType = new() + { + { "Book", DatabaseType.MSSQL } + }; + + Dictionary bookPermissions = GraphQLTestHelpers.CreateStubEntityPermissionsMap( + new string[] { "Book" }, + new EntityActionOperation[] { EntityActionOperation.Read }, + new string[] { "anonymous" }); + + // Act + DocumentNode queryRoot = QueryBuilder.Build( + root, + entityNameToDatabaseType, + new(new Dictionary { { "Book", GraphQLTestHelpers.GenerateEmptyEntity() } }), + inputTypes: new(), + entityPermissionsMap: bookPermissions, + _isAggregationEnabled: true + ); + + // Assert: find BookConnection type + ObjectTypeDefinitionNode bookConnection = queryRoot.Definitions + .OfType() + .FirstOrDefault(d => d.Name.Value == "BookConnection"); + Assert.IsNotNull(bookConnection, "BookConnection type should exist"); + + FieldDefinitionNode groupByField = bookConnection.Fields.FirstOrDefault(f => f.Name.Value == "groupBy"); + Assert.IsNotNull(groupByField, "groupBy field should exist on BookConnection when aggregation is enabled for MSSQL"); + } + + /// + /// Tests that QueryBuilder.Build does NOT add a groupBy field to the connection type + /// for PostgreSQL entities, since aggregation is only enabled for MSSQL and DWSQL. + /// + [TestMethod] + [TestCategory("Query Builder - Aggregation")] + public void Build_WithPostgreSqlAndAggregationEnabled_DoesNotAddGroupByToConnectionType() + { + // Arrange + string gql = @" +type Book @model(name:""Book"") { + id: ID! + price: Float! + title: String +}"; + + DocumentNode root = Utf8GraphQLParser.Parse(gql); + Dictionary entityNameToDatabaseType = new() + { + { "Book", DatabaseType.PostgreSQL } + }; + + Dictionary bookPermissions = GraphQLTestHelpers.CreateStubEntityPermissionsMap( + new string[] { "Book" }, + new EntityActionOperation[] { EntityActionOperation.Read }, + new string[] { "anonymous" }); + + // Act + DocumentNode queryRoot = QueryBuilder.Build( + root, + entityNameToDatabaseType, + new(new Dictionary { { "Book", GraphQLTestHelpers.GenerateEmptyEntity() } }), + inputTypes: new(), + entityPermissionsMap: bookPermissions, + _isAggregationEnabled: true + ); + + // Assert: find BookConnection type + ObjectTypeDefinitionNode bookConnection = queryRoot.Definitions + .OfType() + .FirstOrDefault(d => d.Name.Value == "BookConnection"); + Assert.IsNotNull(bookConnection, "BookConnection type should exist"); + + FieldDefinitionNode groupByField = bookConnection.Fields.FirstOrDefault(f => f.Name.Value == "groupBy"); + Assert.IsNull(groupByField, "groupBy field should NOT exist on BookConnection for PostgreSQL (not in AggregationEnabledDatabaseTypes)"); + } + public static ObjectTypeDefinitionNode GetQueryNode(DocumentNode queryRoot) { return (ObjectTypeDefinitionNode)queryRoot.Definitions.First(d => d is ObjectTypeDefinitionNode node && node.Name.Value == "Query"); From 5fcfec048971e39eb79d2f21ca6133c4386648cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:21:35 +0000 Subject: [PATCH 3/4] Refactor: extract shared GQL constant and permissions helper in QueryBuilderTests Agent-Logs-Url: https://github.com/Azure/data-api-builder/sessions/b4c70fb5-8795-4c8a-a904-d54344395c93 Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- .../GraphQLBuilder/QueryBuilderTests.cs | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs index 9e7feb303f..56cac93c8a 100644 --- a/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs @@ -18,6 +18,16 @@ public class QueryBuilderTests { private const int NUMBER_OF_ARGUMENTS = 4; + /// + /// GQL schema for a Book entity with numeric fields, used for aggregation tests. + /// + private const string BOOK_WITH_NUMERIC_FIELDS_GQL = @" +type Book @model(name:""Book"") { + id: ID! + price: Float! + title: String +}"; + private Dictionary _entityPermissions; /// @@ -37,6 +47,14 @@ public void SetupEntityPermissionsMap() ); } + private static Dictionary CreateBookEntityPermissions() + { + return GraphQLTestHelpers.CreateStubEntityPermissionsMap( + new string[] { "Book" }, + new EntityActionOperation[] { EntityActionOperation.Read }, + new string[] { "anonymous" }); + } + [DataTestMethod] [TestCategory("Query Generation")] [TestCategory("Single item access")] @@ -566,31 +584,19 @@ public void GenerateReturnType_ExcludesGroupByField_WhenAggregationDisabled() public void Build_WithMssqlAndAggregationEnabled_AddsGroupByToConnectionType() { // Arrange - string gql = @" -type Book @model(name:""Book"") { - id: ID! - price: Float! - title: String -}"; - - DocumentNode root = Utf8GraphQLParser.Parse(gql); + DocumentNode root = Utf8GraphQLParser.Parse(BOOK_WITH_NUMERIC_FIELDS_GQL); Dictionary entityNameToDatabaseType = new() { { "Book", DatabaseType.MSSQL } }; - Dictionary bookPermissions = GraphQLTestHelpers.CreateStubEntityPermissionsMap( - new string[] { "Book" }, - new EntityActionOperation[] { EntityActionOperation.Read }, - new string[] { "anonymous" }); - // Act DocumentNode queryRoot = QueryBuilder.Build( root, entityNameToDatabaseType, new(new Dictionary { { "Book", GraphQLTestHelpers.GenerateEmptyEntity() } }), inputTypes: new(), - entityPermissionsMap: bookPermissions, + entityPermissionsMap: CreateBookEntityPermissions(), _isAggregationEnabled: true ); @@ -613,31 +619,19 @@ type Book @model(name:""Book"") { public void Build_WithPostgreSqlAndAggregationEnabled_DoesNotAddGroupByToConnectionType() { // Arrange - string gql = @" -type Book @model(name:""Book"") { - id: ID! - price: Float! - title: String -}"; - - DocumentNode root = Utf8GraphQLParser.Parse(gql); + DocumentNode root = Utf8GraphQLParser.Parse(BOOK_WITH_NUMERIC_FIELDS_GQL); Dictionary entityNameToDatabaseType = new() { { "Book", DatabaseType.PostgreSQL } }; - Dictionary bookPermissions = GraphQLTestHelpers.CreateStubEntityPermissionsMap( - new string[] { "Book" }, - new EntityActionOperation[] { EntityActionOperation.Read }, - new string[] { "anonymous" }); - // Act DocumentNode queryRoot = QueryBuilder.Build( root, entityNameToDatabaseType, new(new Dictionary { { "Book", GraphQLTestHelpers.GenerateEmptyEntity() } }), inputTypes: new(), - entityPermissionsMap: bookPermissions, + entityPermissionsMap: CreateBookEntityPermissions(), _isAggregationEnabled: true ); From cb6e5eb6812d8cad857999a57286ca7cadc16d9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 01:31:02 +0000 Subject: [PATCH 4/4] Refactor aggregation tests: extract LoadConfig helper, merge parameterized tests - Add LoadConfig helper in RuntimeConfigLoaderTests to eliminate repeated mock filesystem setup - Merge EnableAggregation_WhenExplicitlyDisabled/Enabled into a single DataTestMethod with DataRow(true) and DataRow(false) - Merge Build_WithMssql/PostgreSqlAndAggregationEnabled into a single DataTestMethod covering MSSQL, DWSQL, PostgreSQL, and MySQL Agent-Logs-Url: https://github.com/Azure/data-api-builder/sessions/7c3dcc98-f816-4bc1-b5cd-2228bf05e5cb Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- .../Configuration/RuntimeConfigLoaderTests.cs | 111 +++++------------- .../GraphQLBuilder/QueryBuilderTests.cs | 61 ++++------ 2 files changed, 48 insertions(+), 124 deletions(-) diff --git a/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs b/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs index 77d0fd3ecd..d0ecbc1ce3 100644 --- a/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs +++ b/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs @@ -528,18 +528,8 @@ public void EnableAggregation_WhenGraphQLSectionAbsent_DefaultsToTrue() ""entities"": {} }"; - IFileSystem fs = new MockFileSystem(new Dictionary() - { - { "dab-config.json", new MockFileData(configJson) } - }); + RuntimeConfig runtimeConfig = LoadConfig(configJson); - FileSystemRuntimeConfigLoader loader = new(fs); - - // Act - bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); - - // Assert - Assert.IsTrue(loaded, "Config should load successfully."); Assert.IsNull(runtimeConfig.Runtime?.GraphQL, "GraphQL section should be null for this config."); Assert.IsTrue(runtimeConfig.EnableAggregation, "EnableAggregation should default to true when runtime.graphql section is absent."); @@ -561,102 +551,57 @@ public void EnableAggregation_WhenRuntimeSectionAbsent_DefaultsToTrue() ""entities"": {} }"; - IFileSystem fs = new MockFileSystem(new Dictionary() - { - { "dab-config.json", new MockFileData(configJson) } - }); - - FileSystemRuntimeConfigLoader loader = new(fs); + RuntimeConfig runtimeConfig = LoadConfig(configJson); - // Act - bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); - - // Assert - Assert.IsTrue(loaded, "Config should load successfully."); Assert.IsNull(runtimeConfig.Runtime, "Runtime section should be null for this config."); Assert.IsTrue(runtimeConfig.EnableAggregation, "EnableAggregation should default to true when runtime section is absent."); } /// - /// Tests that EnableAggregation returns false when explicitly disabled in config. + /// Tests that EnableAggregation honours the explicit value set in the config file. /// - [TestMethod] - public void EnableAggregation_WhenExplicitlyDisabled_ReturnsFalse() + [DataTestMethod] + [DataRow(true, DisplayName = "Explicit true is respected")] + [DataRow(false, DisplayName = "Explicit false is respected")] + public void EnableAggregation_WhenExplicitlySet_ReturnsConfiguredValue(bool explicitValue) { - // Arrange: a config with enable-aggregation explicitly set to false - string configJson = @"{ + string configJson = $@"{{ ""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"", - ""data-source"": { + ""data-source"": {{ ""database-type"": ""mssql"", ""connection-string"": ""Server=tcp:127.0.0.1,1433;"" - }, - ""runtime"": { - ""graphql"": { + }}, + ""runtime"": {{ + ""graphql"": {{ ""enabled"": true, - ""enable-aggregation"": false - }, - ""host"": { - ""authentication"": { ""provider"": ""StaticWebApps"" } - } - }, - ""entities"": {} - }"; - - IFileSystem fs = new MockFileSystem(new Dictionary() - { - { "dab-config.json", new MockFileData(configJson) } - }); - - FileSystemRuntimeConfigLoader loader = new(fs); + ""enable-aggregation"": {explicitValue.ToString().ToLower()} + }}, + ""host"": {{ + ""authentication"": {{ ""provider"": ""StaticWebApps"" }} + }} + }}, + ""entities"": {{}} + }}"; - // Act - bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); + RuntimeConfig runtimeConfig = LoadConfig(configJson); - // Assert - Assert.IsTrue(loaded, "Config should load successfully."); - Assert.IsFalse(runtimeConfig.EnableAggregation, - "EnableAggregation should be false when explicitly set to false in config."); + Assert.AreEqual(explicitValue, runtimeConfig.EnableAggregation, + $"EnableAggregation should be {explicitValue} when explicitly set to {explicitValue} in config."); } /// - /// Tests that EnableAggregation returns true when explicitly enabled in config. + /// Loads a from a JSON string using a mock file system. /// - [TestMethod] - public void EnableAggregation_WhenExplicitlyEnabled_ReturnsTrue() + private static RuntimeConfig LoadConfig(string configJson) { - // Arrange: a config with enable-aggregation explicitly set to true - string configJson = @"{ - ""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"", - ""data-source"": { - ""database-type"": ""mssql"", - ""connection-string"": ""Server=tcp:127.0.0.1,1433;"" - }, - ""runtime"": { - ""graphql"": { - ""enabled"": true, - ""enable-aggregation"": true - }, - ""host"": { - ""authentication"": { ""provider"": ""StaticWebApps"" } - } - }, - ""entities"": {} - }"; - - IFileSystem fs = new MockFileSystem(new Dictionary() + IFileSystem fs = new MockFileSystem(new Dictionary { { "dab-config.json", new MockFileData(configJson) } }); FileSystemRuntimeConfigLoader loader = new(fs); - - // Act - bool loaded = loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig); - - // Assert - Assert.IsTrue(loaded, "Config should load successfully."); - Assert.IsTrue(runtimeConfig.EnableAggregation, - "EnableAggregation should be true when explicitly set to true in config."); + Assert.IsTrue(loader.TryLoadConfig("dab-config.json", out RuntimeConfig config), "Config should load successfully."); + return config; } } diff --git a/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs index 56cac93c8a..93a3df9778 100644 --- a/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/QueryBuilderTests.cs @@ -575,19 +575,26 @@ public void GenerateReturnType_ExcludesGroupByField_WhenAggregationDisabled() } /// - /// Tests that QueryBuilder.Build adds a groupBy field to the connection type - /// for MSSQL entities when aggregation is enabled. This verifies the fix for the - /// regression where aggregation features were not accessible in the schema. + /// Tests that QueryBuilder.Build correctly adds or omits the groupBy field on the + /// connection type based on whether the database type is in + /// . + /// MSSQL and DWSQL are supported; other types (e.g. PostgreSQL) are not. /// - [TestMethod] + [DataTestMethod] + [DataRow(DatabaseType.MSSQL, true, DisplayName = "MSSQL: groupBy field present when aggregation enabled")] + [DataRow(DatabaseType.DWSQL, true, DisplayName = "DWSQL: groupBy field present when aggregation enabled")] + [DataRow(DatabaseType.PostgreSQL, false, DisplayName = "PostgreSQL: groupBy field absent (not in AggregationEnabledDatabaseTypes)")] + [DataRow(DatabaseType.MySQL, false, DisplayName = "MySQL: groupBy field absent (not in AggregationEnabledDatabaseTypes)")] [TestCategory("Query Builder - Aggregation")] - public void Build_WithMssqlAndAggregationEnabled_AddsGroupByToConnectionType() + public void Build_WithAggregationEnabled_GroupByPresenceMatchesDatabaseSupport( + DatabaseType dbType, + bool expectGroupBy) { // Arrange DocumentNode root = Utf8GraphQLParser.Parse(BOOK_WITH_NUMERIC_FIELDS_GQL); Dictionary entityNameToDatabaseType = new() { - { "Book", DatabaseType.MSSQL } + { "Book", dbType } }; // Act @@ -607,42 +614,14 @@ public void Build_WithMssqlAndAggregationEnabled_AddsGroupByToConnectionType() Assert.IsNotNull(bookConnection, "BookConnection type should exist"); FieldDefinitionNode groupByField = bookConnection.Fields.FirstOrDefault(f => f.Name.Value == "groupBy"); - Assert.IsNotNull(groupByField, "groupBy field should exist on BookConnection when aggregation is enabled for MSSQL"); - } - - /// - /// Tests that QueryBuilder.Build does NOT add a groupBy field to the connection type - /// for PostgreSQL entities, since aggregation is only enabled for MSSQL and DWSQL. - /// - [TestMethod] - [TestCategory("Query Builder - Aggregation")] - public void Build_WithPostgreSqlAndAggregationEnabled_DoesNotAddGroupByToConnectionType() - { - // Arrange - DocumentNode root = Utf8GraphQLParser.Parse(BOOK_WITH_NUMERIC_FIELDS_GQL); - Dictionary entityNameToDatabaseType = new() + if (expectGroupBy) { - { "Book", DatabaseType.PostgreSQL } - }; - - // Act - DocumentNode queryRoot = QueryBuilder.Build( - root, - entityNameToDatabaseType, - new(new Dictionary { { "Book", GraphQLTestHelpers.GenerateEmptyEntity() } }), - inputTypes: new(), - entityPermissionsMap: CreateBookEntityPermissions(), - _isAggregationEnabled: true - ); - - // Assert: find BookConnection type - ObjectTypeDefinitionNode bookConnection = queryRoot.Definitions - .OfType() - .FirstOrDefault(d => d.Name.Value == "BookConnection"); - Assert.IsNotNull(bookConnection, "BookConnection type should exist"); - - FieldDefinitionNode groupByField = bookConnection.Fields.FirstOrDefault(f => f.Name.Value == "groupBy"); - Assert.IsNull(groupByField, "groupBy field should NOT exist on BookConnection for PostgreSQL (not in AggregationEnabledDatabaseTypes)"); + Assert.IsNotNull(groupByField, $"groupBy field should exist on BookConnection for {dbType}"); + } + else + { + Assert.IsNull(groupByField, $"groupBy field should NOT exist on BookConnection for {dbType} (not in AggregationEnabledDatabaseTypes)"); + } } public static ObjectTypeDefinitionNode GetQueryNode(DocumentNode queryRoot)