diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs
index f5112844da..f3c9a4261f 100644
--- a/src/Core/Configurations/RuntimeConfigValidator.cs
+++ b/src/Core/Configurations/RuntimeConfigValidator.cs
@@ -96,18 +96,6 @@ public void ValidateConfigProperties()
ValidateLoggerFilters(runtimeConfig);
ValidateAzureLogAnalyticsAuth(runtimeConfig);
ValidateFileSinkPath(runtimeConfig);
-
- // Running these graphQL validations only in development mode to ensure
- // fast startup of engine in production mode.
- if (runtimeConfig.IsDevelopmentMode())
- {
- ValidateEntityConfiguration(runtimeConfig);
-
- if (runtimeConfig.IsGraphQLEnabled)
- {
- ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(runtimeConfig.DataSource.DatabaseType, runtimeConfig.Entities);
- }
- }
}
///
@@ -334,6 +322,11 @@ public async Task TryValidateConfig(
_logger.LogInformation("Validating entity relationships.");
ValidateRelationshipConfigCorrectness(runtimeConfig);
+
+ // This function initializes the metadata providers which in turn validates the connectivity to the
+ // database and also validates all the REST and GraphQL paths as well as the permissions of the entities
+ // that are created from the 'Entities' and 'Autoentities' configuration, including the relationships defined in the config against the database metadata.
+ // Any exceptions caught during this process are added to the ConfigValidationExceptions list and logged at the end of this function.
await ValidateEntitiesMetadata(runtimeConfig, loggerFactory);
if (validationResult.IsValid && !ConfigValidationExceptions.Any())
@@ -486,6 +479,8 @@ public void ValidateRelationshipConfigCorrectness(RuntimeConfig runtimeConfig)
/// This method validates the entities relationships against the database objects using
/// metadata from the backend DB generated by this function.
///
+ /// NOTE: This function should not be used in the regular flow of DAB as we already initialize the metadata providers during startup,
+ /// doing it again will cause the application to fail as it will try to add data that is already present.
public async Task ValidateEntitiesMetadata(RuntimeConfig runtimeConfig, ILoggerFactory loggerFactory)
{
// Only used for validation so we don't need the handler which is for hot reload scenarios.
@@ -499,6 +494,7 @@ public async Task ValidateEntitiesMetadata(RuntimeConfig runtimeConfig, ILoggerF
// Only used for validation so we don't need the handler which is for hot reload scenarios.
MetadataProviderFactory metadataProviderFactory = new(
runtimeConfigProvider: _runtimeConfigProvider,
+ runtimeConfigValidator: this,
queryManagerFactory: queryManagerFactory,
logger: loggerFactory.CreateLogger(),
fileSystem: _fileSystem,
@@ -1594,4 +1590,26 @@ private static bool IsLoggerFilterValid(string loggerFilter)
return false;
}
+
+ ///
+ /// Checks that all of the entities created with the Entities and Autoentities properties
+ /// are valid by having unique paths for both REST and GraphQL, that there are no duplicate
+ /// Queries or Mutation entities, and ensure the semantic correctness of all the entities.
+ ///
+ /// The runtime configuration.
+ public void ValidateEntityAndAutoentityConfigurations(RuntimeConfig runtimeConfig)
+ {
+ if (runtimeConfig.IsDevelopmentMode())
+ {
+ ValidateEntityConfiguration(runtimeConfig);
+
+ if (runtimeConfig.IsGraphQLEnabled)
+ {
+ ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(runtimeConfig.DataSource.DatabaseType, runtimeConfig.Entities);
+ }
+
+ // Running only in developer mode to ensure fast and smooth startup in production.
+ ValidatePermissionsInConfig(runtimeConfig);
+ }
+ }
}
diff --git a/src/Core/Services/MetadataProviders/CosmosSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/CosmosSqlMetadataProvider.cs
index 61ffeeab09..5b9b2f935a 100644
--- a/src/Core/Services/MetadataProviders/CosmosSqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/CosmosSqlMetadataProvider.cs
@@ -55,7 +55,7 @@ public class CosmosSqlMetadataProvider : ISqlMetadataProvider
public List SqlMetadataExceptions { get; private set; } = new();
- public CosmosSqlMetadataProvider(RuntimeConfigProvider runtimeConfigProvider, IFileSystem fileSystem)
+ public CosmosSqlMetadataProvider(RuntimeConfigProvider runtimeConfigProvider, RuntimeConfigValidator runtimeConfigValidator, IFileSystem fileSystem)
{
RuntimeConfig runtimeConfig = runtimeConfigProvider.GetConfig();
_fileSystem = fileSystem;
@@ -76,6 +76,7 @@ public CosmosSqlMetadataProvider(RuntimeConfigProvider runtimeConfigProvider, IF
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}
+ runtimeConfigValidator.ValidateEntityAndAutoentityConfigurations(runtimeConfig);
_cosmosDb = cosmosDb;
ParseSchemaGraphQLDocument();
diff --git a/src/Core/Services/MetadataProviders/MetadataProviderFactory.cs b/src/Core/Services/MetadataProviders/MetadataProviderFactory.cs
index 66112fce21..6fe20969ed 100644
--- a/src/Core/Services/MetadataProviders/MetadataProviderFactory.cs
+++ b/src/Core/Services/MetadataProviders/MetadataProviderFactory.cs
@@ -19,6 +19,7 @@ public class MetadataProviderFactory : IMetadataProviderFactory
{
private readonly IDictionary _metadataProviders;
private readonly RuntimeConfigProvider _runtimeConfigProvider;
+ private readonly RuntimeConfigValidator _runtimeConfigValidator;
private readonly IAbstractQueryManagerFactory _queryManagerFactory;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
@@ -26,6 +27,7 @@ public class MetadataProviderFactory : IMetadataProviderFactory
public MetadataProviderFactory(
RuntimeConfigProvider runtimeConfigProvider,
+ RuntimeConfigValidator runtimeConfigValidator,
IAbstractQueryManagerFactory queryManagerFactory,
ILogger logger,
IFileSystem fileSystem,
@@ -34,6 +36,7 @@ public MetadataProviderFactory(
{
handler?.Subscribe(METADATA_PROVIDER_FACTORY_ON_CONFIG_CHANGED, OnConfigChanged);
_runtimeConfigProvider = runtimeConfigProvider;
+ _runtimeConfigValidator = runtimeConfigValidator;
_queryManagerFactory = queryManagerFactory;
_logger = logger;
_fileSystem = fileSystem;
@@ -48,11 +51,11 @@ private void ConfigureMetadataProviders()
{
ISqlMetadataProvider metadataProvider = dataSource.DatabaseType switch
{
- DatabaseType.CosmosDB_NoSQL => new CosmosSqlMetadataProvider(_runtimeConfigProvider, _fileSystem),
- DatabaseType.MSSQL => new MsSqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
- DatabaseType.DWSQL => new MsSqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
- DatabaseType.PostgreSQL => new PostgreSqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
- DatabaseType.MySQL => new MySqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
+ DatabaseType.CosmosDB_NoSQL => new CosmosSqlMetadataProvider(_runtimeConfigProvider, _runtimeConfigValidator, _fileSystem),
+ DatabaseType.MSSQL => new MsSqlMetadataProvider(_runtimeConfigProvider, _runtimeConfigValidator, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
+ DatabaseType.DWSQL => new MsSqlMetadataProvider(_runtimeConfigProvider, _runtimeConfigValidator, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
+ DatabaseType.PostgreSQL => new PostgreSqlMetadataProvider(_runtimeConfigProvider, _runtimeConfigValidator, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
+ DatabaseType.MySQL => new MySqlMetadataProvider(_runtimeConfigProvider, _runtimeConfigValidator, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
_ => throw new NotSupportedException(dataSource.DatabaseTypeNotSupportedMessage),
};
diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
index dad3eeb873..96fa47dcfd 100644
--- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
@@ -33,11 +33,12 @@ public class MsSqlMetadataProvider :
public MsSqlMetadataProvider(
RuntimeConfigProvider runtimeConfigProvider,
+ RuntimeConfigValidator runtimeConfigValidator,
IAbstractQueryManagerFactory queryManagerFactory,
ILogger logger,
string dataSourceName,
bool isValidateOnly = false)
- : base(runtimeConfigProvider, queryManagerFactory, logger, dataSourceName, isValidateOnly)
+ : base(runtimeConfigProvider, runtimeConfigValidator, queryManagerFactory, logger, dataSourceName, isValidateOnly)
{
_runtimeConfigProvider = runtimeConfigProvider;
}
@@ -374,7 +375,7 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
if (addedEntities == 0)
{
- _logger.LogWarning($"No new entities were generated from the autoentity {autoentityName} defined in the configuration.");
+ _logger.LogWarning("No new entities were generated from the autoentity {autoentityName} defined in the configuration.", autoentityName);
}
}
diff --git a/src/Core/Services/MetadataProviders/MySqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MySqlMetadataProvider.cs
index 99336180d5..26098c2d15 100644
--- a/src/Core/Services/MetadataProviders/MySqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/MySqlMetadataProvider.cs
@@ -23,11 +23,12 @@ public class MySqlMetadataProvider : SqlMetadataProvider logger,
string dataSourceName,
bool isValidateOnly = false)
- : base(runtimeConfigProvider, queryManagerFactory, logger, dataSourceName, isValidateOnly)
+ : base(runtimeConfigProvider, runtimeConfigValidator, queryManagerFactory, logger, dataSourceName, isValidateOnly)
{
try
{
diff --git a/src/Core/Services/MetadataProviders/PostgreSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/PostgreSqlMetadataProvider.cs
index ecd65b3d95..0d43d0efbc 100644
--- a/src/Core/Services/MetadataProviders/PostgreSqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/PostgreSqlMetadataProvider.cs
@@ -22,11 +22,12 @@ public class PostgreSqlMetadataProvider :
public PostgreSqlMetadataProvider(
RuntimeConfigProvider runtimeConfigProvider,
+ RuntimeConfigValidator runtimeConfigValidator,
IAbstractQueryManagerFactory queryManagerFactory,
ILogger logger,
string dataSourceName,
bool isValidateOnly = false)
- : base(runtimeConfigProvider, queryManagerFactory, logger, dataSourceName, isValidateOnly)
+ : base(runtimeConfigProvider, runtimeConfigValidator, queryManagerFactory, logger, dataSourceName, isValidateOnly)
{
}
diff --git a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
index c7bc023142..6aa2712468 100644
--- a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
@@ -77,6 +77,8 @@ public abstract class SqlMetadataProvider :
private RuntimeConfigProvider _runtimeConfigProvider;
+ private RuntimeConfigValidator _runtimeConfigValidator;
+
private Dictionary> EntityBackingColumnsToExposedNames { get; } = new();
private Dictionary> EntityExposedNamesToBackingColumnNames { get; } = new();
@@ -108,6 +110,7 @@ private void HandleOrRecordException(Exception e)
public SqlMetadataProvider(
RuntimeConfigProvider runtimeConfigProvider,
+ RuntimeConfigValidator runtimeConfigValidator,
IAbstractQueryManagerFactory engineFactory,
ILogger logger,
string dataSourceName,
@@ -115,6 +118,7 @@ public SqlMetadataProvider(
{
RuntimeConfig runtimeConfig = runtimeConfigProvider.GetConfig();
_runtimeConfigProvider = runtimeConfigProvider;
+ _runtimeConfigValidator = runtimeConfigValidator;
_dataSourceName = dataSourceName;
_databaseType = runtimeConfig.GetDataSourceFromDataSourceName(dataSourceName).DatabaseType;
_logger = logger;
@@ -310,12 +314,7 @@ public string GetEntityName(string graphQLType)
public async Task InitializeAsync()
{
System.Diagnostics.Stopwatch timer = System.Diagnostics.Stopwatch.StartNew();
- if (GetDatabaseType() == DatabaseType.MSSQL)
- {
- await GenerateAutoentitiesIntoEntities(Autoentities);
- }
- GenerateDatabaseObjectForEntities();
if (_isValidateOnly)
{
// Currently Validate mode only support single datasource,
@@ -332,8 +331,20 @@ public async Task InitializeAsync()
}
}
+ if (GetDatabaseType() == DatabaseType.MSSQL)
+ {
+ await GenerateAutoentitiesIntoEntities(Autoentities);
+ }
+
+ // Running these entity validations only in development mode to ensure
+ // fast startup of engine in production mode.
+ RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig();
+ _runtimeConfigValidator.ValidateEntityAndAutoentityConfigurations(runtimeConfig);
+
+ GenerateDatabaseObjectForEntities();
await PopulateObjectDefinitionForEntities();
GenerateExposedToBackingColumnMapsForEntities();
+
// When IsLateConfigured is true we are in a hosted scenario and do not reveal primary key information.
if (!_runtimeConfigProvider.IsLateConfigured)
{
diff --git a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs
index 68c9225b96..91c6ef28bd 100644
--- a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs
+++ b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
+using System.IO.Abstractions;
using System.Net;
using System.Reflection;
using System.Text.Json;
@@ -699,10 +700,14 @@ private static Mock CreateMockSqlQueryStructure(string entity
entityToDatabaseObject.Add(entityName, new DatabaseTable());
Mock mockRuntimeConfigProvider = CreateMockRuntimeConfigProvider(entityName);
+ IFileSystem fileSystem = new FileSystem();
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator runtimeConfigValidator = new(mockRuntimeConfigProvider.Object, fileSystem, loggerValidator.Object);
Mock mockQueryFactory = new();
Mock> mockLogger = new();
Mock mockSqlMetadataProvider = new(
mockRuntimeConfigProvider.Object,
+ runtimeConfigValidator,
mockQueryFactory.Object,
mockLogger.Object,
dataSourceName,
diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index 51face4408..0ef9b67a4b 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -3682,8 +3682,11 @@ public void ValidateGraphQLSchemaForCircularReference(string schema)
FileSystemRuntimeConfigLoader loader = new(fileSystem);
RuntimeConfigProvider provider = new(loader);
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator validator = new(provider, fileSystem, loggerValidator.Object);
+
DataApiBuilderException exception =
- Assert.ThrowsException(() => new CosmosSqlMetadataProvider(provider, fileSystem));
+ Assert.ThrowsException(() => new CosmosSqlMetadataProvider(provider, validator, fileSystem));
Assert.AreEqual("Circular reference detected in the provided GraphQL schema for entity 'Character'.", exception.Message);
Assert.AreEqual(HttpStatusCode.InternalServerError, exception.StatusCode);
Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ErrorInInitialization, exception.SubStatusCode);
@@ -3733,9 +3736,11 @@ type Planet @model(name:""PlanetAlias"") {
});
FileSystemRuntimeConfigLoader loader = new(fileSystem);
RuntimeConfigProvider provider = new(loader);
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator validator = new(provider, fileSystem, loggerValidator.Object);
DataApiBuilderException exception =
- Assert.ThrowsException(() => new CosmosSqlMetadataProvider(provider, fileSystem));
+ Assert.ThrowsException(() => new CosmosSqlMetadataProvider(provider, validator, fileSystem));
Assert.AreEqual("The entity 'Character' was not found in the runtime config.", exception.Message);
Assert.AreEqual(HttpStatusCode.ServiceUnavailable, exception.StatusCode);
Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ConfigValidationError, exception.SubStatusCode);
@@ -5289,6 +5294,12 @@ public async Task TestGraphQLIntrospectionQueriesAreNotImpactedByDepthLimit()
}
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
[TestCategory(TestCategory.MSSQL)]
[DataTestMethod]
[DataRow(true, 4, DisplayName = "Test Autoentities with additional entities")]
@@ -5435,6 +5446,194 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities, int
}
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [TestCategory(TestCategory.MSSQL)]
+ [DataTestMethod]
+ [DataRow("publishers", "uniqueSingularPublisher", "uniquePluralPublishers", "/unique/publisher", "Entity with name 'publishers' already exists. Cannot create new entity from autoentity pattern with definition-name 'PublisherAutoEntity'.", DisplayName = "Autoentities fail due to entity name")]
+ [DataRow("UniquePublisher", "publishers", "uniquePluralPublishers", "/unique/publisher", "Entity publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql singular type")]
+ [DataRow("UniquePublisher", "uniqueSingularPublisher", "publishers", "/unique/publisher", "Entity publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql plural type")]
+ [DataRow("UniquePublisher", "uniqueSingularPublisher", "uniquePluralPublishers", "/publishers", "The rest path: publishers specified for entity: publishers is already used by another entity.", DisplayName = "Autoentities fail due to rest path")]
+ public async Task ValidateAutoentityGenerationConflicts(string entityName, string singular, string plural, string path, string exceptionMessage)
+ {
+ // Arrange
+ Entity publisherEntity = new(
+ Source: new("publishers", EntitySourceType.Table, null, null),
+ Fields: null,
+ Rest: new(Path: path),
+ GraphQL: new(Singular: singular, Plural: plural),
+ Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) },
+ Relationships: null,
+ Mappings: null);
+
+ Dictionary entityMap = new()
+ {
+ { entityName, publisherEntity }
+ };
+
+ Dictionary autoentityMap = new()
+ {
+ {
+ "PublisherAutoEntity", new Autoentity(
+ Patterns: new AutoentityPatterns(
+ Include: new[] { "%publishers%" },
+ Exclude: null,
+ Name: null
+ ),
+ Template: new AutoentityTemplate(
+ Rest: new EntityRestOptions(
+ Enabled: true),
+ GraphQL: new EntityGraphQLOptions(
+ Singular: string.Empty,
+ Plural: string.Empty,
+ Enabled: true
+ ),
+ Health: null,
+ Cache: null
+ ),
+ Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) }
+ )
+ }
+ };
+
+ // Create DataSource for MSSQL connection
+ DataSource dataSource = new(DatabaseType.MSSQL,
+ GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
+
+ // Build complete runtime configuration with autoentities
+ RuntimeConfig configuration = new(
+ Schema: "TestAutoentitiesSchema",
+ DataSource: dataSource,
+ Runtime: new(
+ Rest: new(Enabled: true),
+ GraphQL: new(Enabled: true),
+ Mcp: new(Enabled: false),
+ Host: new(
+ Mode: HostMode.Development,
+ Cors: null,
+ Authentication: new Config.ObjectModel.AuthenticationOptions(
+ Provider: nameof(EasyAuthType.StaticWebApps),
+ Jwt: null
+ )
+ )
+ ),
+ Entities: new(entityMap),
+ Autoentities: new RuntimeAutoentities(autoentityMap)
+ );
+
+ File.WriteAllText(CUSTOM_CONFIG_FILENAME, configuration.ToJson());
+
+ ILoggerFactory loggerFactory = new LoggerFactory();
+ IFileSystem fileSystem = new FileSystem();
+
+ FileSystemRuntimeConfigLoader configLoader = new(fileSystem)
+ {
+ RuntimeConfig = configuration
+ };
+
+ RuntimeConfigProvider configProvider = new(configLoader);
+
+ RuntimeConfigValidator configValidator = new(configProvider, fileSystem, loggerFactory.CreateLogger());
+
+ QueryManagerFactory queryManagerFactory = new(
+ runtimeConfigProvider: configProvider,
+ logger: loggerFactory.CreateLogger(),
+ contextAccessor: null!,
+ handler: null);
+
+ MsSqlMetadataProvider provider = new(
+ configProvider,
+ configValidator,
+ queryManagerFactory,
+ loggerFactory.CreateLogger(),
+ configLoader.RuntimeConfig.DefaultDataSourceName,
+ false);
+
+ try
+ {
+ await provider.InitializeAsync();
+ Assert.Fail("It is expected for DAB to fail due to entities not containing unique parameters.");
+ }
+ catch (DataApiBuilderException ex)
+ {
+ Assert.AreEqual(exceptionMessage, ex.Message);
+ }
+ }
+
+ ///
+ /// Validates the autoentity configuration inside the configuration file and also
+ /// validates that entities created from the autoentity configuration do not generate
+ /// duplicate entities and paths for REST and GraphQL.
+ ///
+ ///
+ [TestCategory(TestCategory.MSSQL)]
+ [TestMethod]
+ public async Task ValidateAutoentitiesConfiguration()
+ {
+ EntityAction entityAction = new(EntityActionOperation.Read, null, null);
+
+ Dictionary autoentityMap = new();
+ string autoentityName = "AutoentityA";
+
+ Autoentity autoentity = new(
+ Patterns: new AutoentityPatterns(
+ Include: new[] { "%patterns%" },
+ Exclude: new[] { "%books%" },
+ Name: "{object}"),
+ Template: new AutoentityTemplate(
+ Rest: new(Enabled: false),
+ GraphQL: new(Enabled: true, Singular: string.Empty, Plural: string.Empty),
+ Health: new(enabled: true),
+ Cache: new(Enabled: true, TtlSeconds: 50)),
+ Permissions: new EntityPermission[] { new("anonymous", new EntityAction[] { entityAction }) });
+
+ autoentityMap.Add(autoentityName, autoentity);
+
+ DataSource dataSource = new(DatabaseType.MSSQL,
+ GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
+
+ RuntimeConfig runtimeConfig = new(
+ Schema: "TestAutoentitiesSchema",
+ DataSource: dataSource,
+ Runtime: new(
+ Rest: new(),
+ GraphQL: new(),
+ Mcp: new(),
+ Host: new(null, null, HostMode.Development)),
+ Entities: new(new Dictionary()),
+ Autoentities: new(autoentityMap));
+
+ const string CUSTOM_CONFIG = "autoentities-validation-config.json";
+
+ File.WriteAllText(CUSTOM_CONFIG, runtimeConfig.ToJson());
+ IFileSystem fileSystem = new FileSystem();
+
+ FileSystemRuntimeConfigLoader loader = new(fileSystem)
+ {
+ RuntimeConfig = runtimeConfig
+ };
+
+ RuntimeConfigProvider provider = new(loader);
+ Mock> loggerMock = new();
+ RuntimeConfigValidator configValidator = new(provider, fileSystem, loggerMock.Object);
+
+ try
+ {
+ await configValidator.TryValidateConfig(CUSTOM_CONFIG, TestHelper.ProvisionLoggerFactory());
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail(ex.Message);
+ }
+ }
+
///
/// Tests the behavior of GraphQL queries in non-hosted mode when the depth limit is explicitly set to -1 or null.
/// Setting the depth limit to -1 is intended to disable the depth limit check, allowing queries of any depth.
diff --git a/src/Service.Tests/CosmosTests/TestBase.cs b/src/Service.Tests/CosmosTests/TestBase.cs
index 8617534776..dfac601023 100644
--- a/src/Service.Tests/CosmosTests/TestBase.cs
+++ b/src/Service.Tests/CosmosTests/TestBase.cs
@@ -21,6 +21,7 @@
using Microsoft.AspNetCore.TestHost;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Newtonsoft.Json.Linq;
@@ -158,7 +159,10 @@ protected WebApplicationFactory SetupTestApplicationFactory()
FileSystemRuntimeConfigLoader loader = new(fileSystem);
RuntimeConfigProvider provider = new(loader);
- ISqlMetadataProvider cosmosSqlMetadataProvider = new CosmosSqlMetadataProvider(provider, fileSystem);
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator validator = new(provider, fileSystem, loggerValidator.Object);
+
+ ISqlMetadataProvider cosmosSqlMetadataProvider = new CosmosSqlMetadataProvider(provider, validator, fileSystem);
Mock metadataProviderFactory = new();
metadataProviderFactory.Setup(x => x.GetMetadataProvider(It.IsAny())).Returns(cosmosSqlMetadataProvider);
diff --git a/src/Service.Tests/GraphQLBuilder/MultiSourceBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/MultiSourceBuilderTests.cs
index 7c40dafcd6..67bfa8d2fe 100644
--- a/src/Service.Tests/GraphQLBuilder/MultiSourceBuilderTests.cs
+++ b/src/Service.Tests/GraphQLBuilder/MultiSourceBuilderTests.cs
@@ -46,11 +46,14 @@ public async Task CosmosSchemaBuilderTestAsync()
RuntimeConfigProvider provider = new(loader);
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator validator = new(provider, fs, loggerValidator.Object);
+
Mock queryManagerfactory = new();
Mock queryEngineFactory = new();
Mock mutationEngineFactory = new();
Mock> logger = new();
- IMetadataProviderFactory metadataProviderFactory = new MetadataProviderFactory(provider, queryManagerfactory.Object, logger.Object, fs, handler: null);
+ IMetadataProviderFactory metadataProviderFactory = new MetadataProviderFactory(provider, validator, queryManagerfactory.Object, logger.Object, fs, handler: null);
Mock authResolver = new();
GraphQLSchemaCreator creator = new(provider, queryEngineFactory.Object, mutationEngineFactory.Object, metadataProviderFactory, authResolver.Object);
diff --git a/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs
index 94665d7c18..ffe636f6db 100644
--- a/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs
+++ b/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System.Collections.Generic;
+using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Azure.DataApiBuilder.Auth;
@@ -392,6 +393,16 @@ private static async Task GetGQLSchemaCreator(RuntimeConfi
Mock cache = new();
DabCacheService cacheService = new(cache: cache.Object, logger: null, httpContextAccessor: httpContextAccessor.Object);
+ // Setup runtime config validator
+ IFileSystem fileSystem = new FileSystem();
+ FileSystemRuntimeConfigLoader configLoader = new(fileSystem)
+ {
+ RuntimeConfig = _runtimeConfig
+ };
+ RuntimeConfigProvider configProvider = new(configLoader);
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator configValidator = new(configProvider, fileSystem, loggerValidator.Object);
+
// Setup query manager factory.
IAbstractQueryManagerFactory queryManagerfactory = new QueryManagerFactory(
runtimeConfigProvider: runtimeConfigProvider,
@@ -402,6 +413,7 @@ private static async Task GetGQLSchemaCreator(RuntimeConfi
// Setup metadata provider factory.
IMetadataProviderFactory metadataProviderFactory = new MetadataProviderFactory(
runtimeConfigProvider: runtimeConfigProvider,
+ runtimeConfigValidator: configValidator,
queryManagerFactory: queryManagerfactory,
logger: metadatProviderLogger.Object,
fileSystem: null,
diff --git a/src/Service.Tests/SqlTests/SqlTestBase.cs b/src/Service.Tests/SqlTests/SqlTestBase.cs
index 16e804f117..4e0fa5249c 100644
--- a/src/Service.Tests/SqlTests/SqlTestBase.cs
+++ b/src/Service.Tests/SqlTests/SqlTestBase.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.IO.Abstractions;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
@@ -279,6 +280,10 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
_queryManagerFactory = new Mock();
Mock httpContextAccessor = new();
string dataSourceName = runtimeConfigProvider.GetConfig().DefaultDataSourceName;
+ IFileSystem fileSystem = new FileSystem();
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator runtimeConfigValidator = new(runtimeConfigProvider, fileSystem, loggerValidator.Object);
+
switch (DatabaseEngine)
{
case TestCategory.POSTGRESQL:
@@ -297,6 +302,7 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
_sqlMetadataProvider =
new PostgreSqlMetadataProvider(
runtimeConfigProvider,
+ runtimeConfigValidator,
_queryManagerFactory.Object,
_sqlMetadataLogger,
dataSourceName);
@@ -317,6 +323,7 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
_sqlMetadataProvider =
new MsSqlMetadataProvider(
runtimeConfigProvider,
+ runtimeConfigValidator,
_queryManagerFactory.Object,
_sqlMetadataLogger,
dataSourceName);
@@ -337,6 +344,7 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
_sqlMetadataProvider =
new MySqlMetadataProvider(
runtimeConfigProvider,
+ runtimeConfigValidator,
_queryManagerFactory.Object,
_sqlMetadataLogger,
dataSourceName);
@@ -357,6 +365,7 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
_sqlMetadataProvider =
new MsSqlMetadataProvider(
runtimeConfigProvider,
+ runtimeConfigValidator,
_queryManagerFactory.Object,
_sqlMetadataLogger,
dataSourceName);
diff --git a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs
index c9ce82180b..4c5782b4ca 100644
--- a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs
+++ b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
+using System.IO.Abstractions;
using System.Net;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
@@ -129,8 +130,13 @@ public void CheckTablePrefix(string schemaName, string tableName, string expecte
queryManagerFactory.Setup(x => x.GetQueryBuilder(It.IsAny())).Returns(queryBuilder);
queryManagerFactory.Setup(x => x.GetQueryExecutor(It.IsAny())).Returns(queryExecutor.Object);
+ IFileSystem fileSystem = new FileSystem();
+ ILogger validatorLogger = new Mock>().Object;
+ RuntimeConfigValidator runtimeConfigValidator = new(runtimeConfigProvider, fileSystem, validatorLogger);
+
SqlMetadataProvider provider = new MsSqlMetadataProvider(
runtimeConfigProvider,
+ runtimeConfigValidator,
queryManagerFactory.Object,
sqlMetadataLogger,
dataSourceName);
@@ -215,11 +221,15 @@ private static async Task CheckExceptionForBadConnectionStringHelperAsync(string
queryManagerFactory.Setup(x => x.GetQueryBuilder(It.IsAny())).Returns(_queryBuilder);
queryManagerFactory.Setup(x => x.GetQueryExecutor(It.IsAny())).Returns(_queryExecutor);
+ IFileSystem fileSystem = new FileSystem();
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator runtimeConfigValidator = new(runtimeConfigProvider, fileSystem, loggerValidator.Object);
+
ISqlMetadataProvider sqlMetadataProvider = databaseType switch
{
- TestCategory.MSSQL => new MsSqlMetadataProvider(runtimeConfigProvider, queryManagerFactory.Object, sqlMetadataLogger, dataSourceName),
- TestCategory.MYSQL => new MySqlMetadataProvider(runtimeConfigProvider, queryManagerFactory.Object, sqlMetadataLogger, dataSourceName),
- TestCategory.POSTGRESQL => new PostgreSqlMetadataProvider(runtimeConfigProvider, queryManagerFactory.Object, sqlMetadataLogger, dataSourceName),
+ TestCategory.MSSQL => new MsSqlMetadataProvider(runtimeConfigProvider, runtimeConfigValidator, queryManagerFactory.Object, sqlMetadataLogger, dataSourceName),
+ TestCategory.MYSQL => new MySqlMetadataProvider(runtimeConfigProvider, runtimeConfigValidator, queryManagerFactory.Object, sqlMetadataLogger, dataSourceName),
+ TestCategory.POSTGRESQL => new PostgreSqlMetadataProvider(runtimeConfigProvider, runtimeConfigValidator, queryManagerFactory.Object, sqlMetadataLogger, dataSourceName),
_ => throw new ArgumentException($"Invalid database type: {databaseType}")
};
@@ -480,8 +490,13 @@ public async Task ValidateExceptionForInvalidResultFieldNames(string invalidFiel
queryManagerFactory.Setup(x => x.GetQueryBuilder(It.IsAny())).Returns(_queryBuilder);
queryManagerFactory.Setup(x => x.GetQueryExecutor(It.IsAny())).Returns(mockQueryExecutor.Object);
+ IFileSystem fileSystem = new FileSystem();
+ Mock> loggerValidator = new();
+ RuntimeConfigValidator runtimeConfigValidator = new(runtimeConfigProvider, fileSystem, loggerValidator.Object);
+
ISqlMetadataProvider sqlMetadataProvider = new MsSqlMetadataProvider(
runtimeConfigProvider,
+ runtimeConfigValidator,
queryManagerFactory.Object,
sqlMetadataLogger,
dataSourceName);
@@ -598,7 +613,7 @@ private static async Task SetupTestFixtureAndInferMetadata()
[DataRow(new string[] { "dbo.%publish%" }, new string[] { }, "{schema}.{object}", new string[] { "publish" }, "")]
[DataRow(new string[] { "dbo.%book%" }, new string[] { "dbo.%books%" }, "{schema}_{object}_exclude_books", new string[] { "book" }, "books")]
[DataRow(new string[] { "dbo.%book%", "dbo.%publish%" }, new string[] { }, "{object}", new string[] { "book", "publish" }, "")]
- [DataRow(new string[] { }, new string[] { "dbo.%book%" }, "{object}", new string[] { "" }, "book")]
+ [DataRow(new string[] { }, new string[] { "dbo.%book%" }, "{object}s", new string[] { "" }, "book")]
public async Task CheckAutoentitiesQuery(string[] include, string[] exclude, string name, string[] includeObject, string excludeObject)
{
// Arrange
diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs
index f9e7043440..c24d8be8a8 100644
--- a/src/Service/Startup.cs
+++ b/src/Service/Startup.cs
@@ -1174,16 +1174,8 @@ private async Task PerformOnConfigChangeAsync(IApplicationBuilder app)
// Now that the configuration has been set, perform validation of the runtime config
// itself.
- // TODO: Task #3131. Need to change validation so that the validation of entities is done after the autoentities are generated and added with the regular entitites.
runtimeConfigValidator.ValidateConfigProperties();
- if (runtimeConfig.IsDevelopmentMode())
- {
- // Running only in developer mode to ensure fast and smooth startup in production.
- // TODO: Task #3131. Need to change validation so that the validation of entities is done after the autoentities are generated and added with the regular entitites.
- runtimeConfigValidator.ValidatePermissionsInConfig(runtimeConfig);
- }
-
IMetadataProviderFactory sqlMetadataProviderFactory =
app.ApplicationServices.GetRequiredService();
await sqlMetadataProviderFactory.InitializeAsync();